Избегание common pitfalls

Понимание синхронизации и ожиданий

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

  • Не использовать browser.sleep() для синхронизации browser.sleep(ms) останавливает выполнение на фиксированное время, что замедляет тесты и не гарантирует стабильность. Вместо этого следует применять explicit waits:

    const EC = protractor.ExpectedConditions;
    const button = element(by.id('submit-button'));
    browser.wait(EC.elementToBeClickable(button), 5000);
    button.click();

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

  • Использовать async/await корректно Protractor с последними версиями NodeJS поддерживает async/await. Пренебрежение этим приводит к неправильной последовательности действий:

    // Плохо
    element(by.id('input')).sendKeys('test');
    element(by.id('submit')).click();
    
    // Правильно
    await element(by.id('input')).sendKeys('test');
    await element(by.id('submit')).click();

    Без await операции могут выполняться одновременно, вызывая ошибки типа element not interactable.

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

Правильный выбор локаторов напрямую влияет на стабильность тестов.

  • Использовать CSS и ID вместо XPath при возможности XPath медленнее и более подвержен ломанию при изменении структуры DOM. Пример:

    // Предпочтительно
    element(by.css('#login-button'));
    
    // Менее надежно
    element(by.xpath('//div[@class="login"]/button'));
  • Избегать жёстких цепочек локаторов Слишком глубокие вложенные локаторы увеличивают риск ошибок при изменении UI. Вместо этого применять более универсальные селекторы и by.cssContainingText:

    element(by.cssContainingText('.menu-item', 'Settings')).click();

Управление состоянием тестов

Нестабильные тесты часто связаны с зависимостью от состояния предыдущих тестов.

  • Использовать beforeEach и afterEach для подготовки и очистки состояния Например, очищение cookies, локального хранилища или возврат на начальную страницу:

    beforeEach(async () => {
      await browser.get('https://example.com');
      await browser.manage().deleteAllCookies();
    });
  • Избегать зависимости от данных из предыдущих тестов Каждый тест должен быть автономным. Если один тест упал — это не должно ломать остальные.

Работа с асинхронными элементами и Angular

Protractor изначально создавался для Angular-приложений, что создаёт особые нюансы:

  • Не отключать waitForAngularEnabled без причины browser.waitForAngularEnabled(false) отключает ожидания Angular. Использовать только для не-Angular страниц, иначе тесты станут нестабильными.

  • Ожидание появления элементов Angular Angular может динамически рендерить элементы. Применение ExpectedConditions.visibilityOf гарантирует, что элемент доступен для взаимодействия:

    const message = element(by.binding('statusMessage'));
    await browser.wait(EC.visibilityOf(message), 5000);

Обработка ошибок и надежность

  • Ловить ошибки при взаимодействии с элементами Элементы могут исчезнуть или быть недоступны. Применение конструкций типа:

    try {
      await element(by.id('delete-button')).click();
    } catch (error) {
      console.warn('Кнопка удаления недоступна', error);
    }

    позволяет тесту корректно обрабатывать непредвиденные ситуации.

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

Организация кода и Page Objects

  • Следовать паттерну Page Object Разделение логики тестов и работы с элементами повышает читаемость и снижает риск ошибок. Пример структуры:

    // login.page.js
    class LoginPage {
      constructor() {
        this.username = element(by.id('username'));
        this.password = element(by.id('password'));
        this.submitBtn = element(by.id('login-button'));
      }
    
      async login(user, pass) {
        await this.username.sendKeys(user);
        await this.password.sendKeys(pass);
        await this.submitBtn.click();
      }
    }
    
    module.exports = new LoginPage();
    // login.spec.js
    const loginPage = require('./login.page');
    
    it('должен успешно войти', async () => {
      await loginPage.login('admin', '1234');
      expect(await element(by.id('welcome-message')).isDisplayed()).toBe(true);
    });

    Такой подход минимизирует дублирование кода и упрощает сопровождение тестов.

Логирование и отладка

  • Добавление логов при критичных шагах Это позволяет быстро выявлять проблемные места в тестах:

    console.log('Кликаем кнопку отправки формы');
    await submitButton.click();
  • Использовать скриншоты при падении теста Protractor поддерживает создание скриншотов через browser.takeScreenshot(), что помогает анализировать причины падений.

Сводка ошибок, которых следует избегать

  1. Неправильная синхронизация с элементами.
  2. Использование sleep() вместо ожиданий.
  3. Глубокие и хрупкие локаторы.
  4. Зависимость тестов друг от друга.
  5. Игнорирование асинхронности Angular-приложений.
  6. Отсутствие Page Object паттерна и повторного использования кода.
  7. Недостаточная обработка ошибок и отсутствие логирования.

Эффективное применение этих практик делает тесты на Protractor стабильными, ускоряет их выполнение и упрощает поддержку большого объема автотестов в реальных проектах.