Как перемещаться по вкладкам при помощи python и seleniumWD?

Фокус сменился у браузера, а у селениума нет. надо явно сказать куда переключить и что закрыть.

Если делаю так

current_window = self.driver.current_window_handle
for handle in self.driver.window_handles:
        if self.driver.switch_to.window(handle) != current_window:
            self.driver.close()

То замечаю в самом конце, что закрывается первая вкладка - именно так, что мне нужна.

Вот так попробовал

from selenium.webdriver.common.keys import Keys
self.driver.find_element_by_tag_name("body").send_keys(Keys.ALT + Keys.NUMPAD1)
self.driver.switch_to.window(current_window)

Не сработало. Просто не реагирует и все(

Немного непонятно чего именно вы добиваетесь этим кодом )

Из всех команд, работает только закрытие. Хоть фокус и находится на вкладке с pdf документом, но при вызове команды

self.driver.close()

Закрывается именно 1 вкладка (которая нужна), хотя она даже не в фокусе.

У меня есть подозрение, что проблема из-за страниц с pdf. Они может какие-то другие.

Значит так… для селениума нет понятия “вкладка” это в браузере она может выглядеть как вкладка, но селениум видит это как отдельное окно. Посему, когда открывается браузер в первый раз, то селениум начинает работать с ним как с первым окном. Потом, если каким-то образом, вручную или по нажатию на кнопки, открываются другие “вкладки”, то в браузере это выгладит как смена контекста, фокуса или чего угодно еще, но селениум по прежнему видит то, самое первое, окно. Соответственно все команды отправляются именно в него, в том числе и close(). Что бы переключится в другие окна, нам надо узнать как их зовут. Для этого используют driver.window_handles - эта команда вернет список имен всех открытых окон (“вкладок”) включая и нашу текущую. Соответственно, что бы переключится в другое окно, нам надо из этого списка выбрать имя, которое отличается от имени текущего окна, а текущее окно можно узнать по driver.current_window_handle. Важно понимать, что окна (вкладки) открываются не мгновенно, потому перед тем, как искать имена открытых окон, надо подождать. Подождали, нашли новое окно, переключились, закрыли, переключились обратно. Селениум не переключается между окнами сам, это задача программиста.

3 лайка

Спасибо. Уж разжевали дальше некуда. Получается, что я запоминаю свою вкладку, потом нажимаю кнопку и у меня открывается еще 2 вкладки, но фокус остается на первой (именно поэтому close закрывает именно первую вкладку, хотя я вижу содержимое третьей). затем я получаю имена всех вкладок/окон. переключаюсь например на третью (которую вижу сейчас) и закрываю ее, то же самое делаю и со второй вкладкой и в итоге остаюсь на своей первоначальной и фокус перевожу тоже на нее.

Отсюда вопрос - как мне переключаться по вкладкам, если хэндлы каждый раз называются по новому?

Не думаю что хендлы меняются. Они постоянные для каждого открытого окна. Хотя в ОС хендл - это обычно целое число, а вот вебдрайвер их как-то по своему формирует и они больше на guid какого-то com+ обьекта похожи.
Вобщем, выколите себе глаза, или завяжите… да, наверное так лучше будет. Забудьте что вы видите. Ваш драйвер не видит ничего, он просто делает.

  1. Вы запускаете тест.
  2. Регистрируете драйвер.
  3. Говорите драйверу инициализировать браузер.
  4. Драйвер дает команду ОС запустить браузер.
  5. ОС выполняет запуск браузера, инициализацию процесса, присвоение ПОЖИЗНЕННОГО хендла процессу, инициализацию окна, присвоение ПОЖИЗНЕННОГО хендла окну (driver.current_window_handle), прорисовку окна, запуск цикла событий, распарареливание потоков в процессе, присвоение ПОЖИЗНЕННЫХ хендлов потокам и т.д. А ваш драйвер в этот момент уже выполняет следующую задачу, если вы ему явно не указали подождать некоторое время. И вот если процесс браузера ещё не зарегестрирован в ОС, драйвер его не увидит, по-этому надо ждать.
  6. Вы делаете штуки с драйвером, драйвер делает штуки с браузером.
  7. Сайт в браузере решает открыть ещё одно окно или закладку браузера. И дает комманду браузеру(не драйверу). Драйвер не знает, что открылось ещё одно окно. Но он может об этом узнать, если спросит у родительского процесса, сколько у него дочерних процессов запущено (self.driver.window_handles)
  8. Вы запоминаете текущее окно, на котором сфокусирован драйвер (current_window = self.driver.current_window_handle)
  9. Переключаетесь на другое окно (driver.switch_to.window(handle)). Если такого хендла нету, то вы останетесь в прежнем (первом) окне.
  10. Делаете что вам надо с другим окном.
  11. Если надо вернуться к первому окну - self.driver.switch_to.window(current_window)
  12. Делаете что вам надо с первым окном.

Хэндлы не меняются. Или это не хендлы. Они постоянные на время жизни обьекта. Когда окно, процесс, обьект закрываются (уничтожаются), хендл затирается. Если создастся новое окно, у него уже будет другой хендл.

PS: Хендл вебдрайвера для окон выглядит как compound кизяк из 90-х годов, че нельзя просто было обычный ОС хендл использовать, нет надо какого-то своего гов**а намутить и главное ниодно руководство не описывает, как они формируют свой хендл ИД и кроме меня этим вопросом по ходу задался только программист который писал эту часть вебдрайвера.

3 лайка

Спасибо вам большое. Я понял всю логику работы. Я понял как это работает и при помощи каких инструментов.

Мне это нужно было, чтоб я просто видел что делает selemium в окне. но в дальнейшем тесты будут запускаться на гриде. Поэтому я забил. Раз фокус находится в том окне где мне нужно, хотя и вижу я другое окно, то пусть так и будет. Там selenium продолжает делать какие-то действия.

Но перемещение по вкладкам у меня так и не получилось сделать.

Нашел такой пример

import unittest
import time
from selenium import webdriver
import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.keys import Keys



class SwitchHandleTest(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(30)
        self.driver.set_page_load_timeout(30)

    def autister_login_test(self, ):
        time.sleep(3)
        self.driver.get('http:/reddit.com')
        time.sleep(3)
        window_before = self.driver.window_handles[0]
        time.sleep(3)
        self.driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')
        time.sleep(3)
        window_after = self.driver.window_handles[1]
        time.sleep(3)
        self.driver.switch_to.window(window_after)
        time.sleep(3)
        self.driver.get('http://bing.com')


    def tearDown(self):
        self.driver.quit()

Но оказалось, что sendkeys в хромдрайвере не работает.

Немного поменял и все получилось. В зависимости от того, что мы ставим в switch_to.windows(after или before) мы перемещаем фокус либо на 1 вкладку, либо во вторую.

import unittest
import time
from selenium import webdriver
import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.keys import Keys



class SwitchHandleTest(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(30)
        self.driver.set_page_load_timeout(30)

    def autister_login_test(self, ):
        self.driver.get('http:/reddit.com')
        time.sleep(3)
        window_before = self.driver.window_handles[0]
        self.driver.execute_script("window.open('http://youtube.com/');")
        time.sleep(3)
        window_after = self.driver.window_handles[1]
        self.driver.switch_to.window(window_after)
        self.driver.get('http://bing.com')
        time.sleep(3)
        self.driver.close()



    def tearDown(self):
        self.driver.quit()

Спасибо всем .что помогли. Задачу решил. Потратил на это 2 дня. Ужасное решение, но самое главное, что работает.

Смоделировал ситуацию, как у меня. 3 вкладки, мне нужно переместиться на первую и продолжить в ней работу, а остальные можно закрыть.

Вот код.

import unittest
import time
from selenium import webdriver
from selenium.webdriver.common.by import By



class SwitchHandleTest(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(30)
        self.driver.set_page_load_timeout(30)

    def autister_login_test(self, ):
        self.driver.get('http:/ya.ru',)
        window_before = self.driver.window_handles[0]
        self.driver.execute_script("window.open('http://youtube.com/');")
        self.driver.execute_script("window.open('http://bing.com/');")
        window_after = self.driver.window_handles[1]
        self.driver.switch_to.window(window_after)
        time.sleep(3)
        self.driver.close()
        window_after1 = self.driver.window_handles[1]
        self.driver.switch_to.window(window_after1)
        self.driver.close()
        self.driver.switch_to.window(window_before)
        self.driver.find_element(By.XPATH, "//input[@id='text']").send_keys('Какой же позорный костыль')
        self.driver.find_element(By.XPATH, "//button[@type='submit']").click()
        time.sleep(3)

    def tearDown(self):
        self.driver.quit()

Все же закрыть другие окна нужно, так как есть определенные действия на странице, которые могут не сработать, если окно не в фокусе, например всяческие обработчики, навешанные на события перерисовки, браузер просто не будет их взвывать, если окно не в фокусе.

1 лайк

Такое решение будет работать в большинстве случаев, но не всегда. Так как список окон, который возвращает window_handles это неупорядоченный список (set), и в нем совсем не гарантируется то, что новые окна будут отсортированы в порядке появления и иметь соответствующие индексы, особенно в ситуациях, когда уже есть открытые окна. Но такие ситуации к счастью довольно редки.

1 лайк

А это надо заменить на явное ожидание:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec

wait = WebDriverWait(driver, 10)
wait.until(ec.new_window_is_opened(old_windows))
#или
wait.until(ec.number_of_windows_to_be(3)) # 2,1 etc.
1 лайк

Я что-то не пойму тут. Что значит вот это?

wait.until(ec.new_window_is_opened(old_windows))

или это

wait.until(ec.number_of_windows_to_be(3)) # 2,1 etc.

Я сделал как вы написали. Но буквально на 5 шаге, где мне надо кликнуть по картинке, мой тест падает. Страница с картинкой после логина просто не успевает загрузиться.

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//div/*/img"}

Вот пример кода моего теста.

        driver = self.driver
        wait = WebDriverWait(driver, 10)  # ожидание
        driver.get(hostname)
        window_before = driver.window_handles[0]
        wait
        driver.find_element(By.XPATH, "//div/input[@name='username']").send_keys(username)
        wait
        driver.find_element(By.XPATH, "//div/input[@name='password']").send_keys(password)
        wait
        driver.find_element(By.XPATH, "//div/input[@name='code']").send_keys(code)
        wait
        driver.find_element(By.XPATH, "//em/button[@type='button']").click()
        wait
        driver.find_element(By.XPATH, "//div/*/img").click()
        wait
        driver.find_element(By.XPATH, "//div/div[contains(text(), 'Оператор')]").click()
        wait

Кажется я нашел где почитать. Извините, что отвлек. Selenium для Python. Глава 5. Ожидания / Habr

Вы не правильно вейт юзаете. Смотрите доки. 5. Waits — Selenium Python Bindings 2 documentation

Хехе. Я попробовал сделать по инструкции и немного запутался. может вы сможете помочь.

driver = self.driver
        wait = WebDriverWait(driver, 10)
        driver.get(hostname)
        window_before = driver.window_handles[0]
        wait.until(lambda driver: driver.find_element(By.XPATH, "//div/input[@name='username']").send_keys(username)) #измененный вариант по документации
    
        driver.find_element(By.XPATH, "//div/input[@name='password']").send_keys(password)

Во время прохождения теста, он не переходит ко второму шагу - ввода пароля, а застревает на первом и 10 секунд вводит логин в поле логин.

Что я делаю не так?

В итоге сделал вот так

        driver = self.driver
        wait = WebDriverWait(driver, 10)
        driver.get(hostname)
        window_before = driver.window_handles[0]
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div/input[@name='username']")))
        element.send_keys(username)
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div/input[@name='password']")))
        element.send_keys(password)
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div/input[@name='code']")))
        element.send_keys(code)
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//em/button[@type='button']")))
        element.click()
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div/*/img")))
        element.click()
        element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div/div[contains(text(), 'Оператор')]")))
        element.click()

И тест падает на том же самом месте когда надо кликнуть по картинке, со слипами такого не происходит.