Скриншоты при ошибках

В e2e-тестировании визуальное состояние страницы в момент ошибки часто является ключевым источником информации. Скриншот фиксирует реальное состояние DOM, верстки, сообщений об ошибках, модальных окон и других элементов, которые могут исчезнуть при повторном запуске теста. В Protractor механизм снятия скриншотов тесно связан с жизненным циклом тестов и используемым тест-раннером (чаще всего Jasmine или Mocha).

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

  • подтверждают факт визуальной проблемы;
  • упрощают анализ нестабильных (flaky) тестов;
  • позволяют выявить расхождения окружений;
  • служат артефактами для CI/CD.

Базовый API Protractor для создания скриншотов

Protractor предоставляет низкоуровневый метод для получения скриншота через WebDriver:

browser.takeScreenshot()

Метод возвращает Promise, который резолвится строкой в формате Base64. Для сохранения изображения требуется самостоятельно записать данные в файл.

Пример минимальной реализации:

const fs = require('fs');
const path = require('path');

browser.takeScreenshot().then(function (png) {
  const stream = fs.createWriteStream(
    path.join(__dirname, 'screenshot.png')
  );
  stream.write(Buffer.from(png, 'base64'));
  stream.end();
});

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


Интеграция со стандартным репортером Jasmine

В Jasmine основной точкой расширения является jasmine.getEnv().addReporter. Через кастомный репортер можно отлавливать момент падения теста и инициировать снятие скриншота.

Пример репортера, сохраняющего скриншот при ошибке:

const fs = require('fs');
const path = require('path');

const screenshotReporter = {
  specDone: function (result) {
    if (result.status === 'failed') {
      browser.takeScreenshot().then(function (png) {
        const fileName = `${result.fullName.replace(/\s+/g, '_')}.png`;
        const filePath = path.join('screenshots', fileName);

        fs.writeFileSync(
          filePath,
          Buffer.from(png, 'base64')
        );
      });
    }
  }
};

jasmine.getEnv().addReporter(screenshotReporter);

Ключевые моменты:

  • используется хук specDone, вызываемый после завершения спецификации;
  • статус failed гарантирует, что скриншот создаётся только при ошибке;
  • имя файла формируется из полного имени теста.

Обработка асинхронности и контроль времени

Protractor работает поверх WebDriverJS, поэтому все операции асинхронны. При падении теста важно, чтобы браузер ещё был доступен и не начал завершение сессии.

Для стабильной работы:

  • снятие скриншота выполняется до завершения afterAll;
  • в CI рекомендуется отключать restartBrowserBetweenTests;
  • при использовании async/await необходимо явно ожидать takeScreenshot.

Пример с async/await:

specDone: async function (result) {
  if (result.status === 'failed') {
    const png = await browser.takeScreenshot();
    fs.writeFileSync(
      `screenshots/${Date.now()}.png`,
      Buffer.from(png, 'base64')
    );
  }
}

Скриншоты при падении afterEach

Иногда ошибка происходит не внутри теста, а в afterEach (например, при очистке состояния). В таком случае specDone может не отработать корректно.

Решение — использование process.on('unhandledRejection') или явный перехват ошибок:

afterEach(async function () {
  if (this.currentTest.state === 'failed') {
    const png = await browser.takeScreenshot();
    fs.writeFileSync(
      `screenshots/${this.currentTest.title}.png`,
      Buffer.from(png, 'base64')
    );
  }
});

Этот подход чаще используется с Mocha, но принцип аналогичен.


Структурирование скриншотов

При большом количестве тестов хаотичное сохранение файлов быстро становится проблемой. Практика показывает эффективность следующей структуры:

screenshots/
 ├── chrome/
 │   ├── login/
 │   └── profile/
 ├── firefox/
 └── edge/

Для этого в имени или пути используются:

  • имя браузера: browser.capabilities.get('browserName');
  • название spec-файла;
  • timestamp или build number из CI.

Пример получения имени браузера:

browser.getCapabilities().then(function (caps) {
  const browserName = caps.get('browserName');
});

Скриншоты в хуке onPrepare

Часто логика скриншотов выносится в onPrepare конфигурационного файла Protractor, чтобы она применялась ко всем тестам централизованно.

Пример в protractor.conf.js:

onPrepare: function () {
  const fs = require('fs');
  const path = require('path');

  jasmine.getEnv().addReporter({
    specDone: function (result) {
      if (result.failedExpectations.length > 0) {
        browser.takeScreenshot().then(function (png) {
          const filePath = path.join(
            'screenshots',
            `${result.id}.png`
          );
          fs.writeFileSync(
            filePath,
            Buffer.from(png, 'base64')
          );
        });
      }
    }
  });
}

Такой подход обеспечивает единообразие поведения без дублирования кода в тестах.


Совмещение со сторонними репортерами

Популярные репортеры (Allure, Protractor HTML Reporter, Jasmine2 HTML Reporter) поддерживают автоматическое прикрепление скриншотов к отчётам.

Пример для Allure:

const allure = require('allure-commandline');

browser.takeScreenshot().then(function (png) {
  allure.addAttachment(
    'Screenshot',
    Buffer.from(png, 'base64'),
    'image/png'
  );
});

В этом случае физическое сохранение файла может быть необязательным, так как артефакт хранится внутри отчёта.


Полноэкранные и частичные скриншоты

browser.takeScreenshot() всегда делает скриншот видимой области браузера. Для полноэкранных скриншотов требуется:

  • прокрутка страницы;
  • использование сторонних библиотек;
  • или запуск браузера с увеличенным viewport.

Частичные скриншоты (конкретного элемента) реализуются через WebDriver API:

const element = $('#error-message');

browser.takeScreenshot().then(function (png) {
  // далее — обрезка изображения через стороннюю библиотеку
});

Сам Protractor не предоставляет встроенной функции для обрезки, поэтому используются sharp, jimp или аналогичные инструменты.


Типовые ошибки и ограничения

  • Пустые скриншоты — браузер уже закрыт или сессия уничтожена.
  • Перезапись файлов — отсутствие уникальных имён.
  • Большой размер артефактов — особенно при параллельных прогонах.
  • Невоспроизводимость — визуальное состояние зависит от таймингов.

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

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

Использование в CI/CD

В CI скриншоты обычно сохраняются как build artifacts. Для этого путь к ним должен быть предсказуемым и относительным.

Пример для Jenkins:

Archive Artifacts: screenshots/**/*.png

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


Практика промышленного применения

В крупных проектах логика скриншотов часто инкапсулируется в отдельный модуль:

module.exports.takeOnFailure = async function (testName) {
  const png = await browser.takeScreenshot();
  fs.writeFileSync(
    `screenshots/${testName}.png`,
    Buffer.from(png, 'base64')
  );
};

Далее этот модуль используется в репортерах, хуках или кастомных ассертах, обеспечивая единый стандарт диагностики ошибок во всём тестовом наборе.