Написание кастомного сервиса

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

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

Сервис в WebdriverIO реализуется как объект с набором методов-хуков, которые вызываются в разные моменты жизненного цикла сессии и тестов. Основные хуки включают:

  • onPrepare(config, capabilities) — вызывается перед запуском тестов. Используется для подготовки среды, логирования информации или запуска внешних сервисов.
  • onWorkerStart(cid, caps, specs, args, execArgv) — вызывается при старте каждого рабочего процесса в параллельном запуске.
  • beforeSession(config, capabilities, specs) — выполняется перед инициализацией сессии WebDriver.
  • before(capabilities, specs) — запускается перед выполнением тестов в рамках сессии.
  • beforeTest(test, context) — выполняется перед каждым тестом.
  • afterTest(test, context, { error, result, duration, passed, retries }) — выполняется после каждого теста.
  • after(capabilities, specs) — вызывается после завершения всех тестов в сессии.
  • onComplete(exitCode, config, capabilities, results) — вызывается после завершения всех процессов WebdriverIO. Используется для финальной отчётности или очистки ресурсов.

Сервис реализуется как класс или объект, экспортируемый модулем Node.js:

class CustomService {
    constructor(options) {
        this.options = options;
    }

    onPrepare(config, capabilities) {
        console.log('Подготовка перед тестами');
    }

    beforeTest(test) {
        console.log(`Начало теста: ${test.title}`);
    }

    afterTest(test, context, { error, result, duration, passed }) {
        if (!passed) {
            console.log(`Тест провалился: ${test.title}`);
        }
    }

    onComplete(exitCode) {
        console.log(`Тесты завершены с кодом выхода: ${exitCode}`);
    }
}

module.exports = CustomService;

Подключение сервиса к конфигурации WebdriverIO

После реализации сервиса его необходимо зарегистрировать в конфигурационном файле wdio.conf.js в разделе services:

exports.config = {
    // ...
    services: [
        [CustomService, { option1: 'value1', option2: 'value2' }]
    ],
    // ...
};

Каждый сервис получает объект с опциями при инициализации, что позволяет создавать конфигурируемые решения, адаптированные под разные проекты.

Взаимодействие с браузером и тестами

Сервисы могут использовать глобальный объект browser WebdriverIO для управления сессией или выполнения вспомогательных действий. Например, можно реализовать автоматическое создание скриншотов при падении теста:

afterTest(test, context, { error, result, duration, passed }) {
    if (!passed) {
        const fileName = `./errorShots/${test.title.replace(/\s+/g, '_')}.png`;
        browser.saveScreenshot(fileName);
        console.log(`Скриншот ошибки сохранён: ${fileName}`);
    }
}

Также сервис может интегрироваться с внешними API, базами данных, CI/CD системами или системами мониторинга. Главное — не нарушать изоляцию тестов, выполняя долгие или блокирующие операции в хуках, которые вызываются перед или после каждого теста.

Передача параметров и настройка

Сервисы WebdriverIO поддерживают передачу параметров через конфигурацию. Это позволяет создавать универсальные сервисы, которые легко адаптируются под разные сценарии. Пример с параметрами таймаута и логирования:

class CustomService {
    constructor(options) {
        this.timeout = options.timeout || 5000;
        this.verbose = options.verbose || false;
    }

    beforeTest(test) {
        if (this.verbose) {
            console.log(`Тест начинается: ${test.title}`);
        }
        browser.setTimeout({ 'implicit': this.timeout });
    }
}

Расширение существующих сервисов

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

const SauceService = require('@wdio/sauce-service').default;

class ExtendedSauceService extends SauceService {
    afterTest(test, context, { passed }) {
        super.afterTest(test, context, { passed });
        if (!passed) {
            console.log('Дополнительная обработка падения теста на Sauce Labs');
        }
    }
}

module.exports = ExtendedSauceService;

Рекомендации по разработке сервисов

  • Изоляция логики: сервис не должен содержать логику самих тестов, его задача — поддержка среды.
  • Обработка ошибок: все методы сервисов должны корректно обрабатывать исключения, чтобы не прерывать выполнение тестового раннера.
  • Асинхронность: при использовании асинхронных операций (API-запросы, файловые операции) методы должны возвращать Promise или быть async.
  • Легковесность: сервисы должны минимизировать нагрузку на тестовый процесс и не блокировать основной поток тестирования.

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

  1. Автоматическая очистка данных перед тестами: удаление тестовых пользователей через API.
  2. Сбор метрик времени выполнения тестов: интеграция с базой данных или внешней аналитикой.
  3. Интеграция с системами уведомлений: отправка сообщений в Slack или Telegram при падении критических тестов.
  4. Расширенные отчёты: сохранение скриншотов, видео или логов отдельно от стандартного репорта.

Создание кастомного сервиса позволяет повысить масштабируемость тестовой инфраструктуры и стандартизировать повторяющиеся процессы без изменения тестовых сценариев. Такой подход делает проект WebdriverIO гибким и поддерживаемым на долгосрочной основе.