ѕоиск
 лиенты

“ипичные ошибки при разработке автоматизированных тестов

ƒл€ автоматизации тестировани€ специалисты компании Ђјпланаї довольно часто используют такой инструмент, как Selenium. ќн бесплатный и свободный в использовании, совместим со всеми основными платформами и браузерами, поддерживает несколько €зыков программировани€, включа€ Java, Python, C#, Ruby и Perl, и имеет р€д других преимуществ. ќднако, из-за некоторых особенностей Selenium при программировании можно допустить досадные ошибки. ќ том, как их избежать рассказывает ведущий инженер-тестировщик Ђјпланыї и преподаватель корпоративного университета Ч ћари€ Ѕайкова. 

     

ѕочти два года € преподаю курс Ђ–азработка автоматизированных тестов с использованием Selenium WebDriverї (јвтоматизаци€ функционального тестировани€ с помощью Selenium) в корпоративном университете Ђјпланыї. ¬ процессе работы мне приходитс€ провер€ть большой объем домашних заданий. «а это врем€ € выделила несколько типичных ошибок, которые чаще всего встречаютс€ у наших студентов Ч начинающих автоматизаторов. ƒавайте рассмотрим их на конкретных примерах.

ѕодводные камни Selenium

ќдна из наиболее распространЄнных ошибок возникает при проверке присутстви€ элемента на странице. ѕроисходит это из-за неверного понимани€ работы метода Ч isDisplayed библиотеки Selenium WebDriver. „асто можно увидеть подобную реализацию:

public boolean isElementPresent(WebElement element){
    return element.isDisplayed();

}

¬спомним, что возвращает нам метод element.isDisplayed(); 

  • False, если элемент есть на странице, но он невидимый
  • NoSuchElementException, если элемента на странице нет

ћетод можно примен€ть, только если элемент присутствует в коде страницы. ≈сли же элемента нет, тест упадет с исключением Ч NoSuchElementException.

¬ качестве правильной реализации можно использовать следующие варианты:

public boolean isElementPresent(By locator){
    try{
        DriverManager.getWebDriver().manage().timeouts()
                .implicitlyWait(0, TimeUnit.SECONDS);
        return getWebDriver().findElement(locator).isDisplayed();
    }catch (NoSuchElementException e){
        return false;
    }finally {
        DriverManager.getWebDriver().manage().timeouts()
                .implicitlyWait(30, TimeUnit.SECONDS);
    }
}

¬ блоке try перед выполнением метода isDisplayed устанавливаем не€вное ожидание в 0 секунд, чтобы тест не подвисал, в том случае, когда элемент будет отсутствовать на странице, а метод findElement будет искать его в течение времени, заданного в implicitlyWait.

≈сли элемент не найден, то перехватываем в блоке catch исключение NoSuchElementException и возвращаем false.

¬ блоке finally возвращаем не€вное ожидание в значение по умолчанию дл€ дальнейшего выполнени€ теста.

≈сть еще один вариант реализации, в котором не придетс€ использовать try catch и обрабатывать исключительную ситуацию:

public boolean isElementPresent(By locator){
    getWebDriver().manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
    List elementList = getWebDriver().findElements(locator);
    getWebDriver().manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    if (elementList.size() > 0){
        return elementList.get(0).isDisplayed()
    }
    return false;
}

¬ данном случае используетс€ метод findElements, который возвращает список элементов, найденных по заданному локатору. ≈сли подход€щих веб-элементов нет, то возвращаетс€ пустой список.

ƒл€ этого варианта также необходимо мен€ть не€вное ожидание дл€ метода findElements во избежание зависани€ теста.

Ќеобдуманное использование try-catch

≈ще одной частой ошибкой €вл€етс€ необдуманное использование конструкции try Ц catch.

Ќапример, в блоке try реализуетс€ клик по элементу. ≈сли клик не выполнен и было выброшено исключение, то в блоке catch выполн€етс€ еще один клик по тому же элементу.
try {
    clearCart.click();
} catch (StaleElementReferenceException e) {
    clearCart.click();
}

¬ таких случа€х удобно использовать €вные ожидани€:   

Wait  fluentWait = new FluentWait<>(clearCart)
            .withTimeout(5, TimeUnit.SECONDS)
            .pollingEvery(500, TimeUnit.MILLISECONDS)
            .ignoring(StaleElementReferenceException.
class);
    fluentWait.until(WebElement::isEnabled);
    clearCart.click();

ѕример проверки файла:

try {
    utilScenarioSteps.isFileExists(fileFormat, filename);
} catch (Exception e) {
    try {
        utilScenarioSteps.isFileExists(fileFormat, filename);
    } catch (AssertionError ex) {
        utilScenarioSteps.isFileExists(fileFormat filename);
    }
} finally {
    utilScenarioSteps.deleteFile();
}

Ётот вариант можно переписать с использованием FluentWait Ч класса из библиотеки Selenium WebDriver, который дальше будет рассмотрен более подробно:

Wait wait = new FluentWait<>(file).withTimeout(10, TimeUnit.SECONDS);
wait.until(File::exists);

÷иклы дл€ синхронизации

»спользование циклов дл€ синхронизации и ожидани€ веб-элементов также €вл€етс€ не лучшим решением:

public void waitForSomeThing(){
    int timeout =
30000;
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() - start < timeout){

       String actualResult = new     CalculatorResultsPage().getResult(fieldName);
        if (actualResult.equalsIgnoreCase(expectedResult)){
            return;
        }else {
            try {
                Thread.sleep(
1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    Assert.fail("Timeout is over");
}

¬ данном примере провер€ютс€ значени€ в пол€х, которые обновл€ютс€ автоматически. »ногда это обновление может происходить не сразу, поэтому проверка выполн€етс€ в цикле несколько раз, чтобы предусмотреть вариант, в котором пол€ обновились на несколько секунд позже.

Ѕолее правильным решением было бы использовать стандартные €вные ожидани€, которые уже вход€т в Selenium WebDiver. Ќапример, ExpectedConditions.textToBePresentInElement

WebDriverWait wait = new WebDriverWait(DriverManager.getDriver(), 10);
wait.until(ExpectedConditions.textToBePresentInElement(new CalculatorResultsPage().getField(fieldName), expectedResult));

≈сли же стандартных ожиданий недостаточно дл€ решени€ задачи, то можно реализовать свое ожидание:

WebDriverWait wait = new WebDriverWait(DriverManager.getWebDriver(), 30);
wait.until((ExpectedCondition) driver -> {
    String actualResult = new    

 

CalculatorResultsPage().getResult(fieldName);
   
return actualResult.equalsIgnoreCase(expectedResult);
});

»спользование Explicit Wait и FluentWait

–ассмотрим подробнее ожидание FluentWait, которое:

  • явл€етс€ частью пакета org.openqa.selenium.support.ui.FluentWait
  • явл€етс€ родительским классом дл€ WebDriverWait.
  • »мплементирует интерфейс Wait
  • ѕозвол€ет игнорировать исключени€ при поиске элементов на странице
  • ѕозвол€ет указать частоту, с которой FluentWait должна провер€ть определенные услови€
  • ѕозвол€ет указать максимальное врем€ ожидани€ услови€

ќжидание FluentWait поможет справитьс€ с проблемами синхронизации, которые могут встретитьс€ в тесте. ќжидание FluentWait ожидает определенного событи€ или состо€ни€ в течение заданного времени и провер€ет данное условие с определенной частотой.

ѕример метода, который пытаетс€ найти элемент, игнориру€ исключени€ NoSuchElementException и StaleElementReferenceException:

public WebElement findElementByLocator(By locator) {
    Wait wait = new FluentWait<>(BaseSteps.getDriver())
            .withTimeout(5, TimeUnit.SECONDS)
            .pollingEvery(500, TimeUnit.MILLISECONDS)
            .ignoring(NoSuchElementException.class)
            .ignoring(StaleElementReferenceException.class);

    return wait.until(d -> d.findElement(locator));
}

ќжидание FluentWait можно примен€ть не только с объектами класса WebDriver.Ќапример, здесь мы ждем по€влени€ файла в директории:

public void waitUntilFileIsWritten(File file) {
    Wait wait = new FluentWait<>(file)
            .withTimeout(10, TimeUnit.SECONDS)
            .pollingEvery(250, TimeUnit.MILLISECONDS);
    wait.until(File::exists);
}

ћетоды класса FluentWait:

  • ignoring(Class... types)

Ц позвол€ет игнорировать заданные типы исключений

  • pollingEvery(long duration, TimeUnit unit)

Ц устанавливает частоту выполнени€

  • timeoutException(String message, RuntimeException lastException)

Ц выбрасывает исключение TimeOut Exception

  • until(Function isTrue)

Ц выполн€ет вход€щую функцию до тех пор, пока не будет выполнено одно из условий:

  • ‘ункци€ возвращает не null и не false
  • ‘ункци€ выдает исключение, которое не входит в список игнорируемых
  • »стекает врем€ ожидани€
  • ѕрерываетс€ текущий поток
  • withTimeout(long duration, TimeUnit unit)

Ц устанавливает, как долго ждать выполнени€ услови€

  • withMessage(final String message)

Ц устанавливает сообщение, которое будет получено, если условие не выполнитс€

¬ыбор локаторов

≈щЄ одна типична€ ошибка при написании автотестов Ч это выбор локаторов. „тобы еЄ избежать, достаточно придерживатьс€ основных правил:

  • Ќе использовать сгенерированные локаторы
  • Ќе использовать абсолютные локаторы
  • Ќе использовать при построении xpath атрибуты, которые генерируютс€ автоматически

 онвенции €зыка программировани€

—воим студентам € рекомендую использовать конвенции €зыка программировани€, на котором разрабатываютс€ тесты. ¬ дальнейшем это упростит поддержку и сопровождение тестов, а также поможет новым коллегам быстрее влитьс€ в проект.

Ќапример, эти конвенции €зыка Java помогут сделать код хорошо читаемым и аккуратным:

»мена файлов, пакетов

  • ¬ именах пакетов используютс€ только строчные буквы
  • »мена классов должны быть существительными, первые буквы всех слов Ч заглавные

»мена методов, переменных

  • Ќазвани€ методов должны быть глаголами, перва€ буква должна быть строчной, первые буквы внутренних слов Ч заглавные
  • »мена переменных должны начинатьс€ со строчной буквы, внутренние слова Ч с заглавной
  • »мена констант составл€ютс€ из всех заглавных букв, разделенных на слова символом подчеркивани€

—труктурирование кода

  • ћетоды должны быть короткими и выполн€ть только одну задачу (к примеру, почти любой цикл достоин того, чтобы вынести его в особый метод)
  • »мена методов должны быть самодокументированными
  • Ўаблоны ќќѕ должны примен€тьс€ дл€ структурировани€ и облегчени€ воспри€ти€

 од ревью и важное напутствие

¬ заключение хочу напомнить о необходимости периодически проводить код ревью проекта. ќн помогает написать более аккуратный и хорошо документированный код, а также убедитьс€ в том, что написанный код корректен.

» ещЄ. Ќе огорчайтесь, если вам знакомы ситуации, описанные выше. ѕомните, что делать ошибки Ч это важна€ часть развити€!

≈сли у вас возникли вопросы, оставл€йте комментарии под постом о типичных ошибках при разработке автоматизированных тестов в наших группах во ¬ онтакте или на Facebook.