Производительность и оптимизация JS выполнения

Protractor — это инструмент для энд-ту-энд тестирования Angular и не только Angular приложений. Важной частью эффективного тестирования является оптимизация производительности выполнения тестов и корректная организация работы с асинхронным JavaScript-кодом.

Асинхронность и управление потоками выполнения

JavaScript в браузере и Node.js работает на основе событийного цикла. Protractor взаимодействует с браузером через WebDriver, где большинство операций асинхронны.

Основные принципы управления асинхронностью в Protractor:

  • Использование async/await для линейного, читаемого кода.
  • Минимизация цепочек .then(), которые создают избыточные уровни вложенности.
  • Избегание ожиданий через sleep(), которые блокируют выполнение, снижая производительность тестов.

Пример оптимального ожидания элемента:

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

Использование ExpectedConditions гарантирует, что тест не выполняет лишние операции и не простаивает без необходимости.

Параллельное выполнение тестов

Protractor поддерживает параллельное выполнение через конфигурацию multiCapabilities или shardTestFiles. Это позволяет распределять нагрузку между браузерами и ускорять общий прогон тестов.

Пример настройки в protractor.conf.js:

exports.config = {
  capabilities: {
    browserName: 'chrome',
    shardTestFiles: true,
    maxInstances: 3
  },
  framework: 'jasmine'
};

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

Оптимизация селекторов

Производительность сильно зависит от локаторов элементов.

Рекомендации:

  • Использовать CSS-селекторы вместо XPath, так как они быстрее обрабатываются браузером.
  • Минимизировать использование сложных вложенных селекторов.
  • Для Angular-приложений применять by.model, by.binding и by.repeater только при необходимости, поскольку они добавляют дополнительную обработку Angular-специфичных данных.

Пример эффективного CSS-селектора:

const loginInput = element(by.css('input#login'));
await loginInput.sendKeys('user');

Минимизация загрузки и рендеринга страниц

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

  • Использовать browser.get() один раз и работать с состояниями приложения через маршрутизацию.
  • Сохранять сессии и cookies между тестами, чтобы избегать повторного логина.
beforeAll(async () => {
  await browser.get('https://example.com');
  await login('user', 'password'); // Пользователь логинится один раз
});

Эффективное использование ожиданий

Неоптимальные ожидания часто становятся узким местом в производительности:

  • browser.sleep() следует избегать, так как это принудительная задержка.
  • Предпочтение стоит отдавать динамическим ожиданиям с ExpectedConditions.
  • Использование коротких таймаутов и проверка нескольких условий одновременно позволяет уменьшить общую длительность тестов.

Снижение нагрузки на браузер

Чтобы улучшить скорость тестов:

  • Выключать анимации и переходы в CSS/JS во время тестирования.
  • Использовать headless-режим браузера (chromeOptions: { args: ['--headless'] }), который снижает затраты ресурсов на визуализацию.
  • Ограничивать количество одновременно открытых вкладок и окон.

Профилирование и отладка производительности

Protractor позволяет интегрироваться с инструментами Chrome DevTools и Node.js профайлерами. Это помогает выявить узкие места в тестах и приложении:

  • Использовать browser.manage().logs() для анализа производительности загрузки и JS-ошибок.
  • Включение трассировки событий в DevTools помогает понять, где тесты простаивают.

Практика модульности и переиспользования

Оптимизация кода тестов снижает их время выполнения:

  • Вынесение повторяющихся действий в page objects или вспомогательные функции.
  • Минимизация количества действий на каждом шаге теста.
  • Избегание лишних проверок и повторных запросов к DOM.
class LoginPage {
  constructor() {
    this.usernameInput = element(by.css('#username'));
    this.passwordInput = element(by.css('#password'));
    this.submitButton = element(by.css('#submit'));
  }

  async login(user, pass) {
    await this.usernameInput.sendKeys(user);
    await this.passwordInput.sendKeys(pass);
    await this.submitButton.click();
  }
}

Баланс между надежностью и скоростью

Главная цель оптимизации — ускорение тестов без потери стабильности. Чрезмерная экономия на ожиданиях и проверках может привести к нестабильным тестам. Компромисс достигается через:

  • Динамические таймауты, адаптированные под конкретные элементы.
  • Параллельное выполнение при сохранении изоляции тестов.
  • Минимизацию операций, не влияющих на проверяемую функциональность.

Эффективная оптимизация Protractor-тестов требует комплексного подхода: управление асинхронностью, рациональная организация локаторов, параллельное выполнение, контроль загрузки страниц и модульный код. Все эти меры в совокупности позволяют уменьшить время прогонов и повысить стабильность тестового процесса.