ElementNotFound обработка

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

  • NoSuchElementError
  • StaleElementReferenceError
  • тайм-аутов ожидания (TimeoutError)

Причины можно разделить на несколько категорий:

  • асинхронная загрузка страницы или данных;
  • динамическое изменение DOM (Angular, AngularJS);
  • некорректные или слишком хрупкие локаторы;
  • преждевременное обращение к элементу;
  • состояние элемента (скрыт, отключён, перекрыт другим элементом).

Понимание этих причин — основа корректной обработки подобных ошибок.


Асинхронность и синхронизация в Protractor

Protractor изначально создавался как инструмент для Angular-приложений и использует механизм автоматической синхронизации с $digest и $http. Однако этот механизм не гарантирует, что конкретный элемент уже появился в DOM.

Пример проблемной ситуации:

element(by.css('.save-button')).click();

Если кнопка появляется после завершения HTTP-запроса или анимации, тест может завершиться ошибкой.

Ключевой момент: автоматическая синхронизация не заменяет явных ожиданий элементов.


Явные ожидания как основной инструмент защиты

Основной способ обработки ElementNotFound — использование ExpectedConditions.

const EC = protractor.ExpectedConditions;

const saveButton = element(by.css('.save-button'));
browser.wait(EC.presenceOf(saveButton), 5000);
saveButton.click();

Наиболее часто используемые условия:

  • presenceOf — элемент присутствует в DOM;
  • visibilityOf — элемент видим;
  • elementToBeClickable — элемент доступен для клика;
  • stalenessOf — элемент удалён из DOM.

Выбор неправильного условия — частая причина нестабильных тестов. Например, presenceOf не гарантирует, что элемент видим или доступен для взаимодействия.


Работа с коллекциями элементов

Ошибка ElementNotFound часто возникает при работе с element.all, особенно при обращении по индексу.

element.all(by.css('.item')).get(0).click();

Если список пуст, get(0) приведёт к ошибке.

Безопасный подход:

const items = element.all(by.css('.item'));

browser.wait(() =>
  items.count().then(count => count > 0),
  5000
);

items.first().click();

Здесь ожидание строится на реальном состоянии коллекции, а не на предположении о наличии элементов.


Проверка существования элемента без выброса исключения

Метод isPresent() позволяет проверить наличие элемента без генерации ошибки.

const modal = element(by.css('.modal'));

modal.isPresent().then(isPresent => {
  if (isPresent) {
    modal.element(by.css('.close')).click();
  }
});

Важно различать:

  • isPresent() — элемент есть в DOM;
  • isDisplayed() — элемент видим (вызывает ошибку, если элемента нет).

Неправильное использование isDisplayed() — распространённый источник ElementNotFound.


Обработка динамически исчезающих элементов

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

Небезопасный код:

const row = element(by.css('.row'));
row.click();
// DOM обновился
row.element(by.css('.delete')).click();

Корректный подход — повторный поиск элемента:

element(by.css('.row')).click();
browser.wait(EC.presenceOf(element(by.css('.delete'))), 3000);
element(by.css('.delete')).click();

Правило: не хранить ссылки на элементы дольше одного логического шага.


Локаторы как источник ElementNotFound

Слишком сложные или зависящие от структуры DOM локаторы повышают риск ошибок.

Плохой пример:

by.css('div.container > div:nth-child(3) > button')

Лучшие практики:

  • использование data-testid, data-qa, data-e2e;
  • минимизация зависимости от иерархии;
  • предпочтение by.id и by.css с устойчивыми атрибутами.

Пример устойчивого локатора:

by.css('[data-qa="submit-button"]')

Тайм-ауты ожидания и глобальные настройки

Иногда ElementNotFound возникает из-за слишком малого времени ожидания.

Глобальная настройка:

exports.config = {
  allScriptsTimeout: 11000,
  getPageTimeout: 10000
};

Однако увеличение тайм-аутов — не универсальное решение. Приоритет всегда у локальных явных ожиданий, привязанных к конкретным элементам и состояниям.


Оборачивание действий в безопасные функции

Для крупных проектов характерно создание утилитарных методов:

function safeClick(el, timeout = 5000) {
  const EC = protractor.ExpectedConditions;
  browser.wait(EC.elementToBeClickable(el), timeout);
  return el.click();
}

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

safeClick(element(by.id('submit')));

Такой подход:

  • централизует обработку ElementNotFound;
  • упрощает поддержку тестов;
  • снижает дублирование кода.

Логирование и диагностика

При отладке ошибок ElementNotFound важно фиксировать состояние страницы:

  • URL;
  • HTML-фрагменты;
  • скриншоты;
  • текущие ожидания.

Пример получения HTML:

browser.getPageSource().then(source => {
  console.log(source);
});

Это особенно полезно при падениях в CI, где воспроизведение локально затруднено.


Связь ElementNotFound с архитектурой тестов

Частые ошибки ElementNotFound — симптом проблем в архитектуре:

  • отсутствие Page Object;
  • прямые обращения к элементам из тестов;
  • логика ожиданий размазана по тестам.

Page Object позволяет инкапсулировать ожидания:

this.save = function () {
  const btn = element(by.id('save'));
  browser.wait(EC.elementToBeClickable(btn), 5000);
  btn.click();
};

В результате тесты становятся декларативными и устойчивыми к изменениям интерфейса.


Итоговые принципы обработки ElementNotFound

  • никогда не полагаться на мгновенное появление элементов;
  • использовать явные ожидания, соответствующие реальному состоянию элемента;
  • не хранить устаревшие ссылки на DOM-элементы;
  • проектировать устойчивые локаторы;
  • инкапсулировать работу с элементами;
  • рассматривать ElementNotFound как сигнал архитектурной проблемы, а не просто ошибку времени выполнения.