Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Метод sendKeys теряет символы при параллельном запуске тестов.

maven-surefire-plugin
java
selenium
webdriver
Теги: #<Tag:0x00007f7b656bdce8> #<Tag:0x00007f7b656bdb08> #<Tag:0x00007f7b656bd9c8> #<Tag:0x00007f7b656bd888>

(Дмитрий Кравчук) #1

Дано:
ChromeDriver 2.25.426923 + junit + maven
Задача:
По ходу тестов нужно заполнять поля с автокомплитерами.
Проблема:
Запуск в 8 потоков. Случайным образом при заполнении поля может потеряться один два символа.
Пример.

sendKeys("Казань")

Поле заполнено - “Казнь”.

Мой костыль:

public void sendKeysByChars(WebElement we, String charSequence){
        char[] charArray = charSequence.toCharArray();
        int charNumber = 0;
        for (char character : charArray){
            while (true) {
                we.sendKeys(String.valueOf(character));
                String text = we.getAttribute("value");
                if(text.charAt(charNumber)==character){
                    break;
                }
            }
            charNumber++;
        }
    }

Вопрос:
Встречался ли кто нибудь с такой бякой? Если да, то как вы решили эту проблему?


(Yaroslav Pernerovskyy) #2

Интересно как у вас реализован запуск параллельно и как между потоками инстасы драйвера живут.


(Алексей Берлин) #3

Ваще не понимаю, есть последовательность символов: charSequence, которая приводится к массиву символов, что впринципе одно и тоже. Зачем символы к символам приводить? - я что-то не понимаю видимо. Плюс мне кажется для такой простой задачи слишком много линеек кода пишеться. Нету этого - ну как его принципа - KISS.

Кстати если не ошибаюсь метод sendKeys() уже вводит имеюющиеся символы, зачем, спрашивается их парсить в символы ?


(Sergey QA) #4

Я считаю, что такое издевательство над глазами как приведенное выше недопустимо…

А по теме, кроме весьма извращенного метода ввода текста, ВебДрайвер не потокобезопасный и его стоит изолировать, если не сделано до этого.


(Roma Marinsky) #5

Единственная бяка - это ваш костыль из приведения строки в массив чар, цикл foreach по char[], с вложенным ещё одним, в котором приведение из чар в стринг и при этом сам sendKeys принимает CharSequence, в итоге ещё одно приведение. И это торжество завершает счётчик в foreach :scream::scream::scream:

Как вы 8 потоков запускаете?

Попробуйте убрать ваш костылёк и использовать просто sendKeys, и попробуйте отправлять латинские символы :slight_smile:


(Sergey Korol) #6

Дайте угадаю: запуск 8 инстансов хрома производится в параллели на 1 машине?

Update: не заметил слова “автокомплитерами”. Можно чуть больше подробностей? Как это у вас реализовано на сайте? Есть паблик линк?


(Дмитрий Кравчук) #7

Я начинающий тестировщик. Мне не очень понятно к чему столько сарказма. Если я что-то не так написал, приношу свои извинения. Но не стоит всех мерить по себе.

По теме, запуск тестов осуществляется с помощью maven.

Конфиг:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.plugin}</version>
<configuration>
    <parallel>classes</parallel>
    <perCoreThreadCount>false</perCoreThreadCount>
    <threadCount>8</threadCount>
    <testFailureIgnore>true</testFailureIgnore>
    <argLine>
        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspect.jweaver.version}/aspectjweaver-${aspect.jweaver.version}.jar"
    </argLine>
    <properties>
        <property>
            <name>listener</name>
            <value>ru.yandex.qatools.allure.junit.AllureRunListener</value>
        </property>
    </properties>
</configuration>
<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspect.jweaver.version}</version>
    </dependency>
</dependencies>
</plugin>

Драйвер инициализируется внутри тестовых классов *Test:

@Before
    public void initBrowser()  {
        System.setProperty("webdriver.chrome.driver", "chromedriver.exe");
        driver = new ChromeDriver();
}

@After
    public void closeBrowser() {
        driver.quit();
}

Ресурс корпоративный, публичной ссылки нет.


(Sergey QA) #8

Попробуйте изолировать поток браузера(вот тут пример: http://pastebin.com/bbNp6SzC) - мне помогло, все работает отлично локально.


(Oleksandr Khotemskyi) #9

Все набросились на этот костыль, а на деле человек прав, и попытался решить проблему своими силами.

@rmarinsky код как для workaround нормальный, не цепляйся )

Это известная проблема. Драйвер реально бывает проглатывает часть символов и не печатает их. Вот к примеру тема где люди обсуждают эту же проблему - https://github.com/angular/protractor/issues/3196
Эту багу то чинят, то она опять всплывает

Есть несколько вариантов

  1. Первонаперво попробовал бы на самых свежих версиях. Обновил бы драйвер до последнего (кажется 2.27 у хромдрайвера сейчас), обновить Selenium Webdriver, и сам Хром браузер

  2. Если не помогает - можно печатать медленней - это так себе вариант, но можно попробовать спать по 10 миллисекунд например между каждой буквой (цифру подобрать экспериментально). Подойдет как временный солюшн

  3. Просто вставлять текст в поле через executeScript document.getElementById('gadget_url').value = ''; что-то вроде такого. Скорость, быстрота, но теряется эмуляция нажатий, если у поля есть какая то логика при введении текста - скорее всего она не сработает


(Yaroslav Pernerovskyy) #10

Не по теме, но вместо этого:

String pathToChromeDriver =          Paths.get("./src/test/resources/ChromeDriver/chromedriver_linux").toAbsolutePath().toString();
       System.setProperty("webdriver.chrome.driver", pathToChromeDriver);

Попробуйте webdrivermanager https://github.com/bonigarcia/webdrivermanager


(Дмитрий Кравчук) #11

Спасибо большое за ответы, первый пункт вроде помог (обновил хромдрайвер до 2.27). Погоняю еще тесты и отпишусь.
Update. Проблема не решилась.
Соорудил новый костыль, работает быстрее. :slight_smile: Позже перепишу тесты с использованием ThreadLocal.

public void sendKeysAndCheck(WebElement we, String charSequence){
    for(int i = 0; i < 3; ){
        we.clear();         
        we.sendKeys(charSequence);
        if(charSequence.equals(we.getAttribute("value")))
            return;
    }
    throw new AssertionError("Поле заполнено не корректно");
}

Надеюсь специалисты по костылям меня этим костылем бить не будут :slight_smile:


(Dmitry Minchuk) #12

Интересный вопрос, на самом деле. Ждем предложений от Aleksej_Berlin, seitar18, rmarinsky, которые блеснули красноречием, но ничего не сказали по сути вопроса.

Мне помогали слипы (как предложил xotabu4), когда такое происходило. SendKeys был обернут в метод, который вводил символ, делал либо слип, либо любое другое лишь бы что, и затем следующий символ. Причем происходило оно не всегда. На некоторых порталах работало в несколько потоков на Хроме без проблем.


(Roma Marinsky) #13
    private static InheritableThreadLocal<WebDriver> webDriver = new InheritableThreadLocal<WebDriver>();
    private static InheritableThreadLocal<Actions> actions = new InheritableThreadLocal<Actions>();

    public static void setDriver(String browserName) {
        webDriver.set(ThreadGuard.protect(BrowserFactory.openBrowser(browserName)));
    }

    public static void setDriver(String browserName, String version) {
        webDriver.set(ThreadGuard.protect(BrowserFactory.openVersionBrowser(browserName, version)));
    }

    public static WebDriver getDriver(){
        return webDriver.get();
    }

(Roma Marinsky) #14

Дмитрий так и не описал как он 8 потоков своих запускает. От этого многое зависит


(Sergey Korol) #15

Если у вас где-то уже определены методы ввода и получения атрибута, то можно сделать еще компактней и универсальней при помощи библиотеки awaitility + java 8 фич. Выглядеть это будет приблизительно следующим образом:

        protected void type(WebElement element, CharSequence text) {
		await().atMost(duration)
			   .until(() -> clearAndType.andThen(e -> valueOf(e).equals(text)).apply(element, text));
	}

Где clearAndType -> BiFunction<WebElement, CharSequence, WebElement>.

П.С. Вы же осознаете, что создали бесконечный цикл? И если по какой-то причине (к примеру, бага) атрибут никогда не будет совпадать с переданным текстом, то ваш тест просто повесит поток.


(Vladislav Kulasov) #16

Такая проблема, как у ТС, у меня возникла когда на поле был повешан скрипт проверяющий ввод пользователя на каждый символ и скрипт сам этот символ вводил в поле, в результате символы проскакивали так как селениум быстро вводил, а JS в браузере не успевал. Решилось только тем, что принудительно в нужное место с помощью JS прописывалось значение. Возможно что при большой нагрузке как раз JS в браузере и не успевает обрабатывать.


(Дмитрий Кравчук) #17

Огромное спасибо за такое бурное обсуждение. Итак по порядку:

Тесты запускаю командой mvn test. Насколько я понимаю, за запуск тестов и генерацию отчетов в maven отвечает maven-surefire-plugin. Часть pom.xml с настройками этого плагина я прикрепил в комментарии выше. Количество потоков определяется строкой:

<threadCount>8</threadCount>

В каждом потоке выполняется тестовый класс целиком

<parallel>classes</parallel>

Т.е. методы одного тестового класса будут выполнены последовательно. Перед запуском каждого тестового класса вызывается:

public void initBrowser()  {
        System.setProperty("webdriver.chrome.driver", "chromedriver.exe");
        driver = new ChromeDriver();
}

После:

public void closeBrowser() {
        driver.quit();
}

Создавая первый костыль, я осознавал, что цикл бесконечный. И да, потоки он вешал. На этот случай я приделал еще один костыль :slight_smile:. Но потом отказался от использования метода sendKeysByChars.

Возможно у меня схожая ситуация с той, которая была у Vladislav Kulasov.
На поле повешен скрипт:

$("input[name=someName]").die().live('keydown.autocomplete', function () {....});

Пока второй костыль справляется и трех попыток хватает для заполнения поля без потерь символов. Буду пробовать внедрять предложенные вами решения, по мере понимания их работы.


(Ramon Menezes) #18

попробуйте сделать копипаст значения в поле поиска, мне в свое время помогло, этим вы обходите валидацию данных by JS


(Евгений Бухгаммер) #19

Кажется, если вы обходите таким образом валидацию JS - то вы априори не проверяете кейс “как по-человечески вставить текст и чтобы валидация его не сломала”.

Ну т.е. если таким образом валидация обходится, у меня вопрос: значит, пользователь используя какие-нибудь автокомплитеры или менеджеры паролей - сможет копипастом вставить что-то и обойти проверку?

Нужно максимально по-человечески вводить текст :slight_smile:


(Ramon Menezes) #20

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