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

Из-за некоторых особенностей Selenium при программировании можно допустить досадные ошибки. О том, как их избежать рассказывает ведущий инженер-тестировщик «Апланы» и преподаватель корпоративного университета — Мария Байкова.

Для автоматизации тестирования специалисты компании «Аплана» довольно часто используют такой инструмент, как 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

       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 extends RuntimeException>... types)
    позволяет игнорировать заданные типы исключений
  • pollingEvery(long duration, TimeUnit unit)
    устанавливает частоту выполнения
  • timeoutException(String message, RuntimeException lastException)
    выбрасывает исключение TimeOut Exception
  • until(Function super T, V> isTrue)
    выполняет входящую функцию до тех пор, пока не будет выполнено одно из условий:
    • Функция возвращает не null и не false
    • Функция выдает исключение, которое не входит в список игнорируемых
    • Истекает время ожидания
    • Прерывается текущий поток
  • withTimeout(long duration, TimeUnit unit)
    устанавливает, как долго ждать выполнения условия
  • withMessage(final String message)
    устанавливает сообщение, которое будет получено, если условие не выполнится

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

Ещё одна типичная ошибка при написании автотестов — это выбор локаторов. Чтобы её избежать, достаточно придерживаться основных правил:

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

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

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

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

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

  • В именах пакетов используются только строчные буквы
  • Имена классов должны быть существительными, первые буквы всех слов — заглавные

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

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

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

  • Методы должны быть короткими и выполнять только одну задачу (к примеру, почти любой цикл достоин того, чтобы вынести его в особый метод)
  • Имена методов должны быть самодокументированными
  • Шаблоны ООП должны применяться для структурирования и облегчения восприятия

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

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

И ещё. Не огорчайтесь, если вам знакомы ситуации, описанные выше. Помните, что делать ошибки — это важная часть развития!

Если у вас возникли вопросы, оставляйте комментарии под постом о типичных ошибках при разработке автоматизированных тестов в наших группах во ВКонтакте или на Facebook.

Советуем прочесть

Сайт IBS использует cookie. Это дает нам возможность следить за корректной работой сайта, а также анализировать данные, чтобы развивать наши продукты и сервисы. Посещая сайт, вы соглашаетесь с обработкой ваших персональных данных. В случае несогласия вам следует покинуть его. Узнать подробности