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

Из-за некоторых особенностей 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. Это дает нам возможность следить за корректной работой сайта, а также анализировать данные, чтобы развивать наши продукты и сервисы. Посещая сайт, вы соглашаетесь с обработкой ваших персональных данных.