Выполнение JS в контексте браузера

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

Метод browser.executeScript

Метод executeScript позволяет выполнить произвольный JavaScript на странице. Его синтаксис следующий:

browser.executeScript(script, ...args)
  • script — функция или строка JavaScript-кода, которая будет выполнена в браузере.
  • args — опциональные аргументы, которые будут переданы в функцию.

Пример использования:

// Получение заголовка страницы через JS
const title = await browser.executeScript('return document.title;');
console.log(title);

Если передать функцию:

const innerText = await browser.executeScript(function(element) {
    return element.innerText;
}, elementFinder);

В этом примере elementFinder — это объект Protractor, который будет автоматически преобразован в соответствующий DOM-элемент внутри браузера.

Метод browser.executeAsyncScript

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

browser.executeAsyncScript(script, ...args)

Пример:

const result = await browser.executeAsyncScript(function(callback) {
    setTimeout(function() {
        callback('Асинхронный результат');
    }, 1000);
});
console.log(result); // 'Асинхронный результат'

Асинхронное выполнение особенно важно для работы с AJAX-запросами или задержками на странице.

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

Protractor предоставляет ElementFinder и ElementArrayFinder для работы с элементами. При выполнении скриптов через executeScript ElementFinder автоматически преобразуется в реальный DOM-элемент:

const elem = element(by.css('.my-class'));
const text = await browser.executeScript(function(el) {
    return el.textContent;
}, elem);
console.log(text);

Для массивов элементов можно использовать executeScript аналогично:

const elems = element.all(by.css('.item'));
const texts = await browser.executeScript(function(elements) {
    return elements.map(e => e.textContent);
}, elems.getWebElements());
console.log(texts);

Модификация элементов через JS

Выполнение JavaScript позволяет напрямую менять свойства элементов, стили или атрибуты:

const elem = element(by.id('my-button'));
await browser.executeScript(function(el) {
    el.style.backgroundColor = 'red';
    el.setAttribute('disabled', 'true');
}, elem);

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

Доступ к глобальным объектам страницы

executeScript предоставляет доступ к глобальным объектам браузера, таким как window, document или любые пользовательские объекты:

const userData = await browser.executeScript('return window.userData;');
console.log(userData);

Можно использовать сложные конструкции, например, вызов функций, определенных на странице:

await browser.executeScript('return window.calculateSum(5, 10);');

Ограничения и особенности

  • Код выполняется в контексте браузера, поэтому Node.js-специфические объекты, такие как require, недоступны.
  • Любые DOM-операции выполняются синхронно, кроме использования executeAsyncScript.
  • Результаты, возвращаемые из executeScript, автоматически сериализуются, поэтому функции и DOM-элементы возвращены не будут — только данные примитивного типа или объекты.

Практические сценарии использования

  1. Извлечение сложных данных с динамических страниц: удобно, когда данные загружаются через JavaScript и стандартные локаторы не дают доступа.
  2. Манипуляция DOM для тестирования состояний: изменение стилей, добавление или удаление атрибутов.
  3. Асинхронные операции: проверка работы AJAX-запросов и обработка колбеков на странице.
  4. Вызов глобальных функций страницы: тестирование логики, встроенной в приложение, без изменения исходного кода.

Рекомендации по использованию

  • Минимизировать объем кода, выполняемого через executeScript, чтобы не создавать сложные зависимости от внутренней реализации страницы.
  • Предпочтительно использовать селекторы Protractor для обычных действий (клики, ввод текста), а JS-скрипты — только для уникальных случаев.
  • При работе с асинхронными скриптами всегда убедиться, что callback вызывается корректно, чтобы избежать зависаний тестов.

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