Минимизация DOM запросов

Одним из ключевых факторов повышения стабильности и производительности автоматизированных тестов в Protractor является минимизация прямых запросов к DOM. Частые обращения к элементам страницы замедляют выполнение тестов, увеличивают вероятность флейковости и делают код трудночитаемым.


Принципы работы с элементами

Protractor работает поверх WebDriverJS и использует промисы для асинхронного взаимодействия с браузером. Каждый вызов методов вроде element() или $$() инициирует запрос к DOM через WebDriver. Частое дублирование этих вызовов приводит к многократным обращением к одним и тем же элементам.

Пример неправильного подхода:

it('Проверка заголовка', async () => {
    expect(await element(by.css('h1')).getText()).toEqual('Главная страница');
    await element(by.css('h1')).click();
});

В этом примере выполняется два запроса к одному и тому же элементу — сначала для получения текста, затем для клика.


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

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

Пример правильного подхода:

it('Проверка заголовка', async () => {
    const header = element(by.css('h1'));
    expect(await header.getText()).toEqual('Главная страница');
    await header.click();
});

Такой подход позволяет:

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

Локаторы и коллекции элементов

При работе с коллекциями элементов (element.all) также важно минимизировать количество обращений.

Неправильный вариант:

const items = element.all(by.css('.item'));
await expect(await items.count()).toBe(5);
await items.get(0).click();
await items.get(1).click();

Каждый вызов get() делает новый запрос к DOM.

Оптимизированный вариант:

const items = await element.all(by.css('.item')).asElementFinders_();
expect(items.length).toBe(5);
await items[0].click();
await items[1].click();

Использование asElementFinders_() кэширует элементы, позволяя многократно использовать их без новых запросов к DOM.


Промежуточное сохранение состояний

Для сложных форм и таблиц полезно сохранять состояние элементов, чтобы не перебирать их повторно.

Пример:

const tableRows = await element.all(by.css('table tr')).asElementFinders_();
for (let i = 0; i < tableRows.length; i++) {
    const cells = await tableRows[i].all(by.css('td')).asElementFinders_();
    console.log(await cells[0].getText());
}

Здесь коллекции tableRows и cells кэшируются, что предотвращает повторные обращения к DOM для каждой итерации.


Использование локальных переменных внутри функций

Для часто используемых элементов лучше создавать локальные переменные внутри функций или Page Object.

Page Object пример:

class LoginPage {
    constructor() {
        this.usernameInput = element(by.id('username'));
        this.passwordInput = element(by.id('password'));
        this.loginButton = element(by.css('.login-btn'));
    }

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

Page Object позволяет:

  • Сохранять ссылки на элементы.
  • Исключить повторные поиски элементов в разных тестах.
  • Централизовать изменение локаторов.

Использование browser.executeScript и минимизация getText

Частые вызовы getText() и других методов WebDriver инициируют сетевые запросы к браузеру. Иногда более эффективным способом является использование browser.executeScript для получения данных напрямую через JavaScript.

Пример:

const headerText = await browser.executeScript(
    "return document.querySelector('h1').textContent;"
);
expect(headerText).toEqual('Главная страница');

Этот подход уменьшает накладные расходы WebDriver и ускоряет тест.


Отложенное обращение к элементам

Не стоит хранить старые ссылки на элементы, которые могут быть перерендерены динамическими фреймворками (Angular, React). В таких случаях нужно либо обновлять переменные, либо использовать локаторы в момент необходимости.

Пример безопасного подхода:

const getHeader = () => element(by.css('h1'));

await expect(await getHeader().getText()).toEqual('Главная страница');
await getHeader().click();

Функция getHeader() гарантирует актуальность локатора без дублирования DOM-запросов.


Советы по производительности

  1. Использовать Page Object для централизованного хранения локаторов.
  2. Минимизировать использование element() и element.all() внутри циклов.
  3. Кэшировать элементы при множественных взаимодействиях.
  4. Для больших коллекций использовать asElementFinders_() или map() для предварительной выборки.
  5. По возможности применять browser.executeScript для извлечения данных напрямую.
  6. Избегать хранения ссылок на элементы, которые часто перерендериваются.

Эти практики значительно снижают количество обращений к DOM, повышают стабильность и сокращают время выполнения тестов в Protractor.