Обработка ошибок в асинхронном коде

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

Ключевая особенность: Protractor построен на WebDriverJS и использует промисы для управления асинхронными операциями. Даже простые действия, такие как element(by.css('.btn')).click(), возвращают Promise, который может быть отклонён при проблемах с DOM или синхронизацией.


Использование try/catch с async/await

Самый читаемый способ работы с асинхронным кодом в Protractor — использование async/await. При этом ошибки можно перехватывать с помощью стандартного try/catch.

it('должен кликнуть на кнопку и обработать ошибку', async () => {
    try {
        const button = element(by.css('.submit'));
        await button.click();
    } catch (error) {
        console.error('Ошибка при клике на кнопку:', error.message);
    }
});

Особенности:

  • try/catch ловит ошибки выполнения Promise, включая NoSuchElementError и ElementNotInteractableError.
  • Позволяет добавлять детальный лог, скриншоты или альтернативные действия при сбое.
  • Улучшает читаемость и поддержку тестов.

Ловля ошибок с помощью .catch()

Если используется цепочка промисов без async/await, ошибки обрабатываются через метод .catch():

element(by.css('.submit')).click()
    .then(() => console.log('Клик выполнен'))
    .catch(error => console.error('Ошибка при клике:', error.message));

Особенности:

  • Подходит для старого стиля Protractor-кода.
  • Ошибка в любой точке цепочки промисов передаётся в ближайший .catch().
  • Требует внимательности: промисы без обработчика .catch() приводят к unhandled promise rejection и падению теста.

Специфические ошибки Protractor

В процессе тестирования часто встречаются специфические ошибки WebDriver:

  1. NoSuchElementError — элемент не найден на странице.
  2. ElementNotVisibleError / ElementNotInteractableError — элемент есть в DOM, но он скрыт или заблокирован.
  3. TimeoutError — ожидание элемента или условия завершилось неудачно.

Для управления этими ошибками можно использовать ожидания (ExpectedConditions) и оборачивать действия в try/catch:

const EC = protractor.ExpectedConditions;
const submitButton = element(by.css('.submit'));

try {
    await browser.wait(EC.elementToBeClickable(submitButton), 5000);
    await submitButton.click();
} catch (error) {
    if (error.name === 'TimeoutError') {
        console.error('Кнопка не стала кликабельной в течение 5 секунд');
    } else {
        console.error('Неизвестная ошибка при клике:', error.message);
    }
}

Обработка ошибок при проверках (assertions)

Ошибки могут возникать не только при действиях, но и при проверках состояния элементов:

const title = element(by.css('h1'));
try {
    const text = await title.getText();
    expect(text).toBe('Добро пожаловать');
} catch (error) {
    console.error('Ошибка при проверке заголовка:', error.message);
}

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

  • Проверки лучше делать внутри try/catch, если требуется дополнительная обработка.
  • Для чистых unit-style assertion лучше позволять тесту падать, чтобы CI фиксировал ошибки автоматически.

Логирование и скриншоты при ошибках

Для анализа сбоев полезно автоматически делать скриншоты при исключениях:

async function takeScreenshot(filename) {
    const png = await browser.takeScreenshot();
    const fs = require('fs');
    fs.writeFileSync(filename, png, { encoding: 'base64' });
}

try {
    await element(by.css('.submit')).click();
} catch (error) {
    console.error('Ошибка при клике:', error.message);
    await takeScreenshot('error.png');
}

Преимущества:

  • Визуальная диагностика проблем.
  • Помогает при нестабильных элементах или сложных анимациях на странице.

Общие рекомендации по обработке ошибок

  • Всегда использовать await вместо простого возврата промиса, чтобы ошибки не терялись.
  • Оборачивать критические операции в try/catch с логированием.
  • Использовать ExpectedConditions для предсказуемости взаимодействий.
  • Разделять ошибки на ожидаемые (например, элемент может отсутствовать) и неожиданные (сбой браузера или теста).
  • Автоматически делать скриншоты при неожиданных ошибках.

Асинхронные цепочки и контроль потока

При работе с несколькими элементами стоит использовать Promise.all с обработкой ошибок:

const buttons = [element(by.id('btn1')), element(by.id('btn2'))];
try {
    await Promise.all(buttons.map(btn => btn.click()));
} catch (error) {
    console.error('Ошибка при клике на один из элементов:', error.message);
}

Особенности:

  • Если один промис отклоняется, все остальные продолжают выполнение, но Promise.all падает с первой ошибкой.
  • Для индивидуальной обработки ошибок каждого действия можно использовать Promise.allSettled:
const results = await Promise.allSettled(buttons.map(btn => btn.click()));
results.forEach((result, index) => {
    if (result.status === 'rejected') {
        console.error(`Ошибка при клике на кнопку ${index + 1}:`, result.reason.message);
    }
});

Эффективная обработка ошибок в Protractor обеспечивает стабильность тестов, упрощает диагностику и предотвращает ложные падения. Ключевой принцип — не игнорировать отклонённые промисы и всегда предусматривать план действий при ошибках.