WebDriver features: Робота с несколькими окнами (вкладками) одновременно при помощи Selenium Web Driver

Это вторая статья из цикла статей "WebDriver features", в которых рассматриваться примеры нестандартных ситуаций, которые могут возникнуть при автоматизации на Selenium WebDriver.

WebDriver features: Робота с несколькими окнами (вкладками) одновременно при помощи Selenium Web Driver

Почти вся автоматизация веб-приложений привязана к одной активной вкладке. То есть при вызове driver.get(“some url”);  откроется вкладка браузера с адресом “some url”. Но что делать, если, например, у нас web приложение, которое состоит из 2-х взаимно зависимых “view-шок”, либо же нам нужно по-быстрому поменять что-то в web интерфейсе сервера или админчасти сайта и проверить эти изменения в одном тесте. Другими словами, конструкция манипуляций выглядит следующим образом:

  1. Start from “some url” in first browser tab

  2. Make some changes (automation test steps)

  3. Open new tab in the same browser with new url (server url, site administration part, etc)

  4. Make some changes in new tab (automation test steps)

  5. Return to first tab

  6. Repeat steps from 2 to 6 till the end of test.

По ходу действий вышеописанного алгоритма нас интересуют такие структурные этапы:

  • создание новой вкладки браузера и открытие в ней новой ссылки тем же webdriver-ом, что используется в тестах

  • навигация между вкладками (туда и обратно) и фокусировка на необходимой вкладке

Итак, разберем по пунктам, что нам нужно знать и реализовать.

Для первого этапа:

  • Объект данного класса WebWindow эмулирует создание новой вкладки браузера. (Для детального изучения данной реализации присутствуют комментарии в коде.

    package Core;
    import org.openqa.selenium.By;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebDriverException;
    import java.util.Set;
    /**
     * Creates and Handles a New window  *
     */
    public class WebWindow {
        private WebDriver driver = PageLoader.GetInstance(); // сюда передаем тот же веб драйвер который вызываеться в самих тестах
        private String handle;
        private String name;
        private String parentHandle;
        private static int instanceCount = 0;
        /**
         * Creates a new window for given web driver
         * @param parent WebDriver instance
         * @param url   Initial url to load
         * @return new WebWindow
         */
        public WebWindow(WebDriver parent, String url) {
            this.driver = parent;
            parentHandle = parent.getWindowHandle();
            name = createUniqueName();
            handle = createWindow(url);
            //Switch to that window and load the url to wait 
            switchToWindow().get(url);
        }
        public String getWindowHandle() {
            return handle;
        }
        public String getParentHandle() {
            return parentHandle;
        }
        public void close() {
            switchToWindow().close();
            handle = "";
            //Switch back to the parent window 
            driver.switchTo().window(parentHandle);
        }
        private static String createUniqueName() {
            return "a_Web_Window_" + instanceCount++;
        }
        public WebDriver switchToWindow() {
            checkForClosed();
            return driver.switchTo().window(handle);
        }
        public WebDriver switchToParent() {
            checkForClosed();
            return driver.switchTo().window(parentHandle);
        }
        private String createWindow(String url) {
            //Record old handles 
            Set < string > oldHandles = driver.getWindowHandles();
            parentHandle = driver.getWindowHandle();
            //Inject an anchor element 
            ((JavascriptExecutor) driver).
            executeScript(
                injectAnchorTag(name, url)
            );
            //Click on the anchor element 
            driver.findElement(By.id(name)).click();
            handle = getNewHandle(oldHandles);
            return handle;
        }
        private String getNewHandle(Set < string > oldHandles) {
            Set < string > newHandles = driver.getWindowHandles();
            newHandles.removeAll(oldHandles);
            //Find the new window 
            for (String handle: newHandles)
                return handle;
            return null;
        }
        private void checkForClosed() {
            if (handle == null || handle.equals(""))
                throw new WebDriverException("Web Window closed or not initialized");
        }
        private String injectAnchorTag(String id, String url) {
            return String.format("var anchorTag = document.createElement('a'); " +
                "anchorTag.appendChild(document.createTextNode('nwh'));" +
                "anchorTag.setAttribute('id', '%s');" +
                "anchorTag.setAttribute('href', '%s');" +
                "anchorTag.setAttribute('target', '_blank');" +
                "anchorTag.setAttribute('style', 'display:block;');" +
                "document.getElementsByTagName('body')[0].appendChild(anchorTag);",
                id, url
            );
        }
    }
    
  • Данному подходу есть имя - Script Injection technique. Использование инъекции anchor tag (with target = _blank …) дает возможность открытия новой вкладки (окна) после клика на этот элемент. 

Пример использования запишется так: ``` WebWindow ww = new WebWindow(driver, "http://google.com");// Откроет новую вкладку окно с url - “http://google.com”. ```

Для второго этапа:

  • Создадим переменную public String handleHost; В аннотации `@Before` к нашим тестам укажем следующее:

@Before
public void setup() {
    …
    handleHost = driver.getWindowHandle(); //handle first Window
    …
}

Другими словами, в нее запишем первую вкладку (окно) с которой стартуют тесты.

  • Напишем методы: создание новой вкладки, переключение со стартовой вкладки(окна) на созданную и обратно.

``` protected void CreateNewTab() { try { WebWindow tab2 = new WebWindow(driver, “url”); // “url” - ссылка новой вкладки } catch (Exception e) { System.err.println("Couldn't load second page"); } }

protected void SwitchFromSecondTabToFirst() {
try {
driver.switchTo().window(handleHost);
driver.switchTo().activeElement();
} catch (Exception e) {
System.err.println(“Couldn’t get back to first page”);
}
}
protected void SwitchFromFirstPageToSecond() {
try {
for (String handle: driver.getWindowHandles()) {
if (handle != handleHost) {
driver.switchTo().window(handle);
driver.switchTo().activeElement();
} // смотрим все вкладки (а их две всего); если і-тая вкладка не равна первой handleHost (инициализированой в пункте (а), тогда переключаемся на нее).
}
} catch (Exception e) {
System.err.println(“Couldn’t get to second page”);
}
}

5 лайков

“view-шок” – классно сказано, звезда в шоке :slight_smile:

Спасибо. Классная статья!

старался) спасибо Вам !

Подскажите, а есть ли такая возможность подкличится к уже открытому браузеру, запущенному например RC ?

Виктор, чесно, я не работал с RC, не могу ответить, суть этого подхода в JS иньекции. Я думаю стоит попробовать. Специфика открытого браузера не должна повлиять на workaround такой стратегии.

На самом деле вопрос стоит в том, что можно ли подконнектиться WebDriver'ом к уже созданному браузеру, не создавая при этом брузер WebDrivera !?

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

вот можно посмотреть сюда за более подробной историей 

http://code.google.com/p/selenium/issues/detail?id=18&colspec=ID%20Stars%20Type%20Status%20Priority%20Milestone%20Owner%20Summary

и там предложенны некоторые решения этой задачи

Простите, за такой вопрос, но дело в том, что у меня данный код не компилируется в IDEA :frowning:

Может я что-то не правильно делаю?

Ошибка вот тут:

WebWindow tab2 = new WebWindow(driver, url);
com.gargoylesoftware.htmlunit.WebWindow is abstract; cannot be instantiated

Использую библиотеку Селениум

<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>2.24.1</version>

Может я не из того пакета WebWindow цепляю?

Но среда не предлагает альтернативного варианта, только вот этот:

       com.gargoylesoftware.htmlunit.WebWindow;

Буду признателен за подсказку.

может вы что то с пекеджами наделали, так как аритектуры тут особой то и нету, это просто клас - ектеншн к фреймворку (дополнение) и он Public. Поместите его в тот же пекедж в котором клас где обьект WebWindow создаеться

Есть следующий вопрос, но он скорее противоположный: Есть ссылка с параметром tarbet="_blank"
Как сделать так что бы при клике на нее не открывалось новое окно

Перед действием с ссылкой надо через javascript удалить аттрибут. Вот эта тема вам пригодиться

1 лайк

Или же driver.get %link% сделать. Да, это не будет эмуляцией действий пользователя, но будет вполне себе переходом по ссылке.

1 лайк
public class WebWindow {
    private WebDriver driver; // = PageLoader.GetInstance(); // сюда передаем тот же веб драйвер который вызываеться в самих тестах
    private String handle; 
    private String name;
    private String parentHandle;
    private static int instanceCount = 0;
    /**

PageLoader. это откуда можно взять, не совсем понятно) может кто пояснит

не знаю может кому то пригодится, еще одно решение

public void openInNewWindow(WebElement url) {
    ((JavascriptExecutor) driver)
            .executeScript("window.open(arguments[0])", url);
}

работает на опере в виде вкладок, на 32 FF как новые окна на 35 FF не работает вообще
может кто то и подскажет почему на разных браузерах по разному работает

Добрый день! Нужна подсказка… Во время отработки этого кода, в строчке driver.findElement(By.id(name)).click(); выдает ошибку:
org.openqa.selenium.WebDriverException: unknown error: Element is not clickable at point (960, 107).

и дальше …
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:216)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:168)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:635)
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:274)
at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:84)
at com.crm_mails.utility.WebWindow.createWindow(WebWindow.java:68)
at com.crm_mails.utility.WebWindow.(WebWindow.java:30)
at com.crm_mails.pages.RamblerPage.getBulkId(RamblerPage.java:130)
at com.crm_mails.pages.RamblerPage.createLetter(RamblerPage.java:126)
at com.crm_mails.pages.RamblerPage.createListOfLetter(RamblerPage.java:94)
at com.crm_mails.tests.ui.RamblerTest.testGetSendingReport(RamblerTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:104)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:645)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:851)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1177)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:756)
at org.testng.TestRunner.run(TestRunner.java:610)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:387)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:382)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:340)
at org.testng.SuiteRunner.run(SuiteRunner.java:289)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1293)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1218)
at org.testng.TestNG.runSuites(TestNG.java:1133)
at org.testng.TestNG.run(TestNG.java:1104)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Еще интересный момент, проект работал пол года назад, все было супер, недавно я развернул его опять и пришлось менять драйвер и пом.xml… Возможно это повлияло на эту ошибку.
Подскажите как решить эту проблему или показать более свежие решения. Заранее спасибо!)

Вам никто не ответит и даже предположить сложно, что у Вас там происходит нужно больше информации, Скриншот страницы с отрытым dev tool, возможно Ваш элемент перекрыт другим элементом, или он есть на странице, но вне области экрана, то есть может ниже или выше и до него нужно скроллить и тд. Может этот элемент впринципе стал некликабельным.
Зачем меняли драйвер и пом? Разверните как было в тот момент, когда работало, поставьте старый браузер, старый драйвер и посмотрите, идите по пути меньшего сопротивления. Если на старых версиях работает, а на новых нет, это один вопрос, если не работает и на старых версиях - это другой вопрос.

Тоже не понимаю что за PageLoader?