сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Автоматизация тестирования

UI-автоматизация тестов

Автоматизация через интерфейс

Я взял экран оплаты и шесть способов найти на нём кнопку «Оплатить». Потом сделал обычный редизайн: обёртка вокруг блока, переименованный класс, кнопки поменялись местами, надпись стала длиннее. Выжили два способа из шести. И один из сломавшихся - самый опасный: он нашёл кнопку «Назад» и продолжил работать как ни в чём не бывало.

Стержень: стабильность тестов через интерфейс держится на устойчивых локаторах и на ожидании условий вместо ожидания времени.

// Формулировки: «как выбираешь локаторы?», «почему sleep плохо?», «как разбирать падение на сервере сборки?».

Локаторы: замер выживаемости

Локатор - это способ объяснить программе, какой именно элемент на странице ей нужен. Вот результат замера, все шесть находили кнопку до редизайна: путь по структуре разметки (/html/body/div/div[2]/button[2]) - не нашёл; поиск по классу оформления .btn-primary - не нашёл, класс переименовали; поиск по порядку («вторая кнопка в блоке») - НАШЁЛ ЧУЖУЮ КНОПКУ «Назад»; поиск по точному тексту «Оплатить» - не нашёл, текст стал «Оплатить заказ»; поиск по data-testid - нашёл; поиск по роли элемента и имени - нашёл.

Самый плохой исход тут не красный тест, а третий случай. Тест остался зелёным и продолжил нажимать не ту кнопку - то есть проверка молча перестала проверять то, ради чего написана. Красный тест ты чинишь за час, а такой можно не заметить месяцами.

// Отсюда порядок предпочтений. Первым - data-testid: специальный атрибут, который фронтенд ставит на важные элементы именно для тестов, и который не трогают при перекраске. Вторым - роль элемента с его подписью (кнопка с именем «Оплатить»). Роль - это чем элемент считается для браузера и для программ чтения с экрана: кнопка, ссылка, поле ввода, заголовок. Она есть у любого осмысленного элемента и переживает перестановку блоков. Дальше - обычные селекторы по стабильным признакам. И в самом конце - путь по структуре разметки, который умирает от первого же изменения.

локатор
способ указать программе конкретный элемент на странице
data-testid
атрибут, который фронтенд ставит специально для тестов; договор, а не оформление

Ожидания: замер трёх стратегий

Второй источник нестабильности - время. Я сделал страницу, где кнопка появляется через 0,8 секунды после загрузки, и попробовал три подхода. «Подожду 3 секунды, наверняка хватит»: 3080 миллисекунд, клик прошёл. «Подожду полсекунды, должно хватить»: упал - элемента ещё нет. Клик с ожиданием самого элемента, без фиксированной паузы: 1317 миллисекунд, клик прошёл.

Первый вариант работает, но платит полную цену всегда: на двухстах тестах эти лишние секунды складываются в минуты простоя. Второй - лотерея: на быстрой машине пройдёт, на загруженном сервере сборки упадёт. Третий возвращается сразу, как только элемент готов, и держит запас времени на случай задержки. Это и есть явное ожидание: ждём условие (элемент видим, кнопка активна, запрос завершился), а не отрезок времени.

// Приятный побочный эффект - падение становится читаемым. Когда я нажал на выключенную кнопку, инструмент подождал полторы секунды и написал прямо: ждал элемент, элемент нашёлся как <button disabled>, пытался кликнуть, ожидал, что он станет видимым, активным и перестанет двигаться. Со sleep на этом месте была бы просто ошибка «клик не удался».

# лотерея: угадываем время
time.sleep(3)
page.click("#pay")

# ожидание условия: вернётся сразу,
# как только кнопка станет активной
page.get_by_test_id("pay").click()
явное ожидание
ждём условие с запасом по времени, а не фиксированную паузу
цена ожидания
лишние 3 секунды на шаг превращаются в минуты на наборе

Короткий путь и следы падения

Правило, которое экономит больше всего: через интерфейс проверяем только то, что проверяем, всё остальное готовим в обход. Вход в аккаунт - подстановкой готового токена (строки-пропуска, которую сервер выдаёт после успешного входа), а не заполнением формы. Товары в корзине, заказы, промокоды - прямыми вызовами к серверу. Если каждый из двухсот тестов честно логинится через форму по 40 секунд, это два с лишним часа на прогон и двести дополнительных мест, где можно поймать нестабильность.

Изоляция обязательна: свои данные и свой пользователь на каждый тест, независимость от порядка. Тест, которому нужен след предыдущего, ломается, как только кто-то поменяет порядок или запустит набор в несколько потоков.

// И про следы падения. Прогон на сервере сборки - на машине, которая сама гоняет тесты после каждого изменения, - отличается от локального: браузер работает без окна, ресурсов меньше, скорость сети другая, таймзона тоже. Поэтому при падении собирают скриншот, видео, запись действий и логи сети. Без них фраза «упало на сервере, локально всё хорошо» превращается в вечную загадку.

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

Как отвечать: «Почему sleep - зло и как ждать правильно?»

Потому что это ставка на фиксированное время в системе, где время плавает. Я мерил три подхода на странице, где кнопка появляется через восемь десятых секунды. Пауза в три секунды - работает, но каждый шаг стоит три секунды, а на двухстах тестах это минуты чистого простоя. Пауза в полсекунды - тест упал, элемента ещё нет; на другой машине он бы прошёл, и получилась бы мигалка. А клик с ожиданием самого элемента занял чуть больше секунды и прошёл: он возвращается сразу, как только условие выполнено, и держит запас времени на случай тормозов. То есть фиксированная пауза одновременно и медленная, и ненадёжная, а ожидание условия быстрее и стабильнее. Ждать нужно события: элемент стал видимым и активным, запрос завершился, индикатор загрузки исчез. Есть и приятный побочный эффект - когда я ждал условие и оно не выполнилось, инструмент написал причину прямым текстом: элемент найден, но он выключен. С паузой на этом месте было бы безликое «клик не удался». Фиксированную паузу оставляю только как временный костыль при отладке.

Почему это сильный ответ: три подхода сравнены замером, названы обе беды паузы (медленно и ненадёжно), дана альтернатива с конкретными условиями и добавлен аргумент про читаемость падения.

На чём валят

  • Локатор по структуре разметки или по классу оформления - из шести способов редизайн пережили два.
  • Локатор «вторая кнопка в блоке»: тест остаётся зелёным и жмёт «Назад» вместо «Оплатить».
  • Фиксированная пауза вместо ожидания: 3 секунды на шаг или падение на загруженном сервере.
  • Каждый тест логинится через форму - 40 секунд на двести тестов это два часа прогона.
  • Не собирать скриншоты и записи при падении - «на сервере упало, локально ок» не разобрать.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.

  1. #ui_automation1 / 5
    Что такое локатор в UI-автоматизации?
    A)Значение, которое тест вводит в поле формы перед отправкой запроса на сервер
    B)Способ найти нужный элемент на странице: по id, CSS-селектору, XPath, тексту или роли
    C)Точка останова в коде теста, на которой прогон приостанавливается для отладки
    D)Отчёт о том, где на странице расположен найденный дефект вёрстки
    показать ответ и разбор
    +B)Способ найти нужный элемент на странице: по id, CSS-селектору, XPath, тексту или роли

    // разбор: Чтобы кликнуть по кнопке или ввести текст, тесту сначала надо найти элемент в DOM — за это отвечает локатор. Виды: по id (#login), CSS-селектору (.btn-primary), XPath (//button[@type='submit']), по видимому тексту или ARIA-роли/подписи. От выбора локатора зависит стабильность теста: привязка к устойчивому id или data-testid переживает перевёрстку, а хрупкий XPath по абсолютной позиции ломается от малейшего сдвига разметки. Хорошие локаторы — однозначные, устойчивые и осмысленные.

  2. #ui_automation2 / 5
    Почему поиск по id/data-testid надёжнее XPath по абсолютной позиции?
    A)XPath по позиции работает быстрее, но требует прав администратора на странице
    B)Id задаёт разработчик, поэтому тестировщик вынужден брать XPath
    C)Стабильный id/data-testid переживает перевёрстку, а XPath по позиции ломается от сдвига структуры DOM
    D)XPath по позиции находит несколько элементов сразу, а id — ровно ноль элементов
    показать ответ и разбор
    +C)Стабильный id/data-testid переживает перевёрстку, а XPath по позиции ломается от сдвига структуры DOM

    // разбор: Абсолютный XPath описывает путь к элементу через структуру: /html/body/div[3]/form/button[2]. Стоит вставить обёртку, поменять порядок блоков или изменить вёрстку — путь съезжает, и локатор перестаёт находить элемент, хотя сам элемент на месте. id и особенно data-testid (атрибут, добавленный специально для тестов) привязаны к самому элементу, а не к его окружению, поэтому переживают рефакторинг разметки. Отсюда правило: предпочитать устойчивые семантические локаторы (id, data-testid, роль/текст) хрупким позиционным путям.

  3. #ui_automation3 / 5
    Почему фиксированный sleep — плохой способ дождаться элемента?
    A)Sleep нагружает процессор ожиданием, поэтому его заменяют на цикл активного опроса
    B)Sleep работает лишь в headless-режиме, а в обычном браузере ожидание игнорируется
    C)Sleep ждёт слишком мало по умолчанию, и достаточно увеличить его до пары минут
    D)sleep либо тормозит (ждёт лишнее время), либо флакает (не дождался под нагрузкой)
    показать ответ и разбор
    +D)sleep либо тормозит (ждёт лишнее время), либо флакает (не дождался под нагрузкой)

    // разбор: Фиксированная пауза (sleep 3с) слепо ждёт заданное время независимо от реальности. Если элемент появился за 0.2с — тест впустую простоял почти три секунды (замедление, помноженное на сотни тестов). Если под нагрузкой страница отрисовалась за 4с — тест не дождался и упал, хотя всё работает (флаки). Угадать «правильное» время нельзя. Решение — явное ожидание условия (wait until элемент виден/кликабелен/текст появился): тест ждёт ровно столько, сколько нужно, и не дольше таймаута. Это одновременно быстрее и надёжнее sleep.

  4. #ui_automation4 / 5
    Чем implicit wait отличается от explicit wait?
    A)Implicit — общий таймаут на поиск элемента; explicit ждёт условия (виден, кликабелен)
    B)Implicit ждёт условия для конкретного элемента, а explicit задаёт общий таймаут на весь тест
    C)Implicit применяют в Playwright, а explicit — в Selenium; это разные фреймворки
    D)Implicit ждёт ответа сервера, а explicit — отрисовки браузером полученного ответа
    показать ответ и разбор
    +A)Implicit — общий таймаут на поиск элемента; explicit ждёт условия (виден, кликабелен)

    // разбор: Implicit wait задаёт глобальный таймаут: при поиске элемента драйвер какое-то время повторяет попытки, прежде чем признать, что его нет. Он прост, но грубоват — ждёт только «появления в DOM», не различая, виден ли элемент и кликабелен, и применяется ко всем поискам сразу. Explicit wait — точечное ожидание конкретного условия для конкретного элемента: «стал видим», «кликабелен», «текст изменился». Он выразительнее и надёжнее для динамических страниц. Их не рекомендуют смешивать: комбинация implicit и explicit даёт непредсказуемые суммарные таймауты.

  5. #ui_automation5 / 5
    Что такое headless-режим браузера?
    A)Режим, в котором браузер выполняет тесты без загрузки JavaScript на странице
    B)Браузер работает без графического окна — так быстрее и удобно в CI, где нет дисплея
    C)Режим, где браузером управляет человек мышью, а не автоматический драйвер
    D)Режим запуска браузера с отключённой сетью для проверки поведения офлайн
    показать ответ и разбор
    +B)Браузер работает без графического окна — так быстрее и удобно в CI, где нет дисплея

    // разбор: В headless-режиме браузер запускается без отрисовки видимого окна: он выполняет всю ту же логику (навигацию, JS, построение DOM), но не тратит ресурсы на графический вывод на экран. Это быстрее и, главное, работает на серверах CI, где дисплея нет вовсе. Обычный (headed) режим удобен при разработке и отладке тестов — видно, что происходит. Нюанс: изредка поведение headless и headed чуть различается (размер вьюпорта, некоторые рендер-эффекты), поэтому критичные визуальные проверки иногда дублируют в headed.

дальше

Теорию прочитали. Навык ставится повторением

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