Работа с вложенными фреймами

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

Основы работы с iframe в Protractor

В Protractor для взаимодействия с элементами внутри iframe используется метод browser.switchTo().frame(). Этот метод позволяет переключить контекст выполнения с основного документа на указанный фрейм.

Синтаксис:

browser.switchTo().frame(iframeWebElement);
  • iframeWebElement — объект типа WebElement, указывающий на нужный фрейм.
  • После переключения все действия element() или by будут относиться к содержимому этого фрейма.

Чтобы вернуться в основной документ, используется:

browser.switchTo().defaultContent();

Получение WebElement iframe

Iframe можно найти стандартными селекторами Protractor. Например:

let myFrame = element(by.css('iframe#myFrame'));

Для вложенных фреймов необходимо последовательно переключаться на каждый уровень:

let outerFrame = element(by.id('outerFrame'));
let innerFrame = element(by.id('innerFrame'));

browser.switchTo().frame(outerFrame.getWebElement());
browser.switchTo().frame(innerFrame.getWebElement());

После выполнения теста важно возвращаться в основной контекст:

browser.switchTo().defaultContent();

Особенности работы с вложенными фреймами

  1. Последовательное переключение: Protractor не поддерживает прямое переключение на deeply nested фрейм, минуя промежуточные. Сначала необходимо переключиться на внешний фрейм, затем на внутренний.
  2. WebElement обязательно использовать: Метод browser.switchTo().frame() принимает только WebElement или индекс, нельзя напрямую передавать объект element() без вызова .getWebElement().
  3. Асинхронность: Все методы работы с элементами внутри фрейма возвращают Promise, поэтому необходимо использовать async/await или подход с then().

Пример с async/await:

it('должен заполнить форму внутри вложенного фрейма', async () => {
    let outerFrame = element(by.id('outerFrame'));
    let innerFrame = element(by.id('innerFrame'));

    await browser.switchTo().frame(await outerFrame.getWebElement());
    await browser.switchTo().frame(await innerFrame.getWebElement());

    let input = element(by.name('username'));
    await input.sendKeys('testuser');

    await browser.switchTo().defaultContent();
});

Использование индексов и имен фреймов

Помимо передачи WebElement, метод frame() поддерживает:

  • Индекс фрейма: browser.switchTo().frame(0) — первый фрейм на странице.
  • Имя или ID фрейма: browser.switchTo().frame('frameName').

При наличии нескольких вложенных фреймов использование индексов требует аккуратного подсчета:

browser.switchTo().frame(0); // Внешний фрейм
browser.switchTo().frame(1); // Внутренний фрейм внутри первого

Работа с элементами после выхода из фрейма

После вызова browser.switchTo().defaultContent() контекст возвращается на основной документ. Любые обращения к элементам внутри фреймов требуют нового переключения.

Для безопасного кода рекомендуется использовать обертки:

async function withFrame(frameElement, callback) {
    await browser.switchTo().frame(await frameElement.getWebElement());
    await callback();
    await browser.switchTo().defaultContent();
}

let frame = element(by.id('innerFrame'));
await withFrame(frame, async () => {
    await element(by.name('password')).sendKeys('secret');
});

Советы по отладке

  • Проверка количества фреймов: element.all(by.tagName('iframe')).count() позволяет убедиться, что нужный фрейм существует.
  • Логирование контекста: можно использовать browser.driver.getTitle() или browser.getCurrentUrl() внутри фрейма для проверки правильного переключения.
  • Ошибки NoSuchElementError: чаще всего появляются из-за того, что контекст не был переключен на нужный фрейм.

Особенности с Angular-приложениями

Protractor изначально ориентирован на Angular, но содержимое iframe может быть как Angular, так и обычным HTML. Для Angular внутри iframe можно продолжать использовать стандартные селекторы by.binding или by.model. Если iframe содержит не-Angular страницу, нужно отключить ожидание Angular:

await browser.waitForAngularEnabled(false);

После работы с не-Angular фреймом необходимо снова включить ожидание:

await browser.waitForAngularEnabled(true);

Практическая схема взаимодействия

  1. Найти внешний iframe через element().
  2. Переключиться на него через .getWebElement().
  3. Если есть вложенный фрейм — повторить шаг 1-2.
  4. Выполнить действия с элементами.
  5. Вернуться в основной документ через defaultContent().
  6. При работе с несколькими тестами — использовать отдельные функции-обертки для надежного управления контекстом.

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