Написание поддерживаемых тестов

Protractor — это инструмент для end-to-end (E2E) тестирования веб-приложений на Angular и других SPA. Его ключевое преимущество — интеграция с Angular, возможность работы с асинхронными операциями и синхронизация с Angular-зоной, что облегчает управление ожиданиями элементов. Для написания поддерживаемых тестов важно соблюдать структурированные подходы и использовать возможности Protractor эффективно.


Конфигурация Protractor

Файл конфигурации protractor.conf.js определяет поведение тестового фреймворка. Основные параметры:

exports.config = {
  framework: 'jasmine',
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['specs/**/*.spec.js'],
  capabilities: {
    browserName: 'chrome'
  },
  baseUrl: 'http://localhost:4200',
  onPrepare: () => {
    browser.waitForAngularEnabled(true);
  },
  jasmineNodeOpts: {
    defaultTimeoutInterval: 30000
  }
};
  • framework — тестовый фреймворк, чаще всего Jasmine или Mocha.
  • specs — путь к тестам. Рекомендуется структурировать тесты по модулям приложения.
  • capabilities — параметры браузера. Поддерживаются Chrome, Firefox и другие.
  • baseUrl — основной URL приложения. Использование baseUrl повышает гибкость тестов.
  • onPrepare — функции, выполняемые до начала тестов, например установка глобальных ожиданий или импорт утилит.

Структура тестов

Тесты в Protractor организуются в виде describe и it блоков:

describe('Авторизация пользователя', () => {
  beforeEach(() => {
    browser.get('/login');
  });

  it('должна отображать форму логина', () => {
    const loginForm = element(by.css('form.login-form'));
    expect(loginForm.isPresent()).toBe(true);
  });

  it('должна успешно авторизовывать пользователя', () => {
    element(by.name('username')).sendKeys('testuser');
    element(by.name('password')).sendKeys('password');
    element(by.buttonText('Войти')).click();

    expect(browser.getCurrentUrl()).toContain('/dashboard');
  });
});

Ключевые моменты:

  • describe — логическая группа тестов.
  • beforeEach — код, выполняющийся перед каждым тестом. Позволяет минимизировать дублирование.
  • it — отдельный тест. Названия тестов должны быть понятными и отражать поведение приложения.

Работа с элементами

Protractor предоставляет несколько способов поиска элементов:

  • by.css(selector) — универсальный селектор CSS.
  • by.id(‘id’), by.className(‘class’), by.name(‘name’) — специализированные селекторы.
  • by.binding(‘angularBinding’), by.model(‘angularModel’) — Angular-специфичные селекторы.
const usernameField = element(by.model('user.username'));
const submitButton = element(by.buttonText('Submit'));

Для коллекций элементов используется element.all:

const items = element.all(by.repeater('item in items'));
expect(items.count()).toBe(5);

Использование коллекций упрощает проверки списков и таблиц.


Асинхронность и ожидания

Protractor управляет асинхронными операциями через Promises, но начиная с версии 5 рекомендуется использовать async/await:

it('должен ждать появления сообщения', async () => {
  const message = element(by.css('.success-msg'));
  await browser.wait(protractor.ExpectedConditions.visibilityOf(message), 5000);
  expect(await message.getText()).toContain('Успешно');
});

Основные моменты:

  • browser.wait с ExpectedConditions позволяет контролировать динамические изменения DOM.
  • Всегда использовать await для получения значений элементов.
  • Избегать использования sleep, чтобы тесты оставались быстрыми и надежными.

Page Object Pattern

Для поддерживаемых тестов рекомендуется применять Page Object — паттерн, отделяющий логику взаимодействия с UI от тестов:

class LoginPage {
  constructor() {
    this.username = element(by.name('username'));
    this.password = element(by.name('password'));
    this.loginButton = element(by.buttonText('Войти'));
  }

  async login(user, pass) {
    await this.username.sendKeys(user);
    await this.password.sendKeys(pass);
    await this.loginButton.click();
  }
}

module.exports = new LoginPage();

Использование в тесте:

const loginPage = require('../pages/login.page');

it('должен авторизовать пользователя', async () => {
  await loginPage.login('testuser', 'password');
  expect(await browser.getCurrentUrl()).toContain('/dashboard');
});

Преимущества:

  • Улучшает читаемость тестов.
  • Снижает дублирование кода.
  • Облегчает поддержку при изменении UI.

Советы по поддерживаемости

  1. Разделение тестов по функциональности — каждая группа тестов должна покрывать отдельный функциональный модуль.
  2. Использование селекторов, устойчивых к изменениям UI — избегать селекторов типа nth-child, лучше использовать id, data-test атрибуты.
  3. Глобальные утилиты и ожидания — создаются в onPrepare, чтобы не дублировать их в каждом тесте.
  4. Логирование и скриншоты — полезно при отладке падений тестов.
afterEach(async () => {
  const png = await browser.takeScreenshot();
  // сохранить png в лог
});
  1. Конфигурация таймаутов — позволяет тестам быть стабильными при медленной загрузке элементов.

Интеграция с CI/CD

Protractor хорошо интегрируется с Jenkins, GitHub Actions, GitLab CI. Рекомендуется запускать E2E тесты параллельно с unit-тестами, используя multiCapabilities для тестирования в разных браузерах:

multiCapabilities: [
  { browserName: 'chrome' },
  { browserName: 'firefox' }
]

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


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