Авторский проект IT-специалиста Олега Барабанова Персональные публикации на тему IT и не только…

Мое знакомство с Playwright на примере организации E2E-тестирования проекта в среде Docker Compose

Начну с того, что некоторое время назад я делал полифил для добавления поддержки псевдокласса :has в DOM API старых браузеров, ну и разумеется этот полифил нужно было хоть как-то протестировать, для чего изначально было задействовано модульное тестирование с использованием Jest и JSDOM.

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

Немного о самом E2E-тестировании

Если упрощенно говорить, то E2E (End-to-End) тестирование представляет собой проверку работы всей системы в целом с максимально возможной эмуляцией рабочего окружения и имитацией реального пользовательского сценария использования.

Глубоко про общую теорию и нюансы E2E-тестирования я тут расписывать не хочу, этого и так в интернете и учебниках полно, просто отмечу, что такое тестирование ни в коем случае не заменяет другие типы тестов, а только дополняет их. Но поскольку E2E-тестирование зачастую довольно затратно и по ресурсам и времени выполнения, то оно обычно запускается уже после успешного выполнения других видов тестов.

В моем случае, в дополнение к модульным тестам (с использованием Jest), мне нужно было отдельно организовать тестирование в различных браузерных движках, благо уже во всю стал среди веб-разработчиков набирать популярность фреймворк Playwright, с которым я и начал знакомиться.

Знакомство с Playwright

Ранее для пользовательского тестирования (и не только) я уже имел опыт использования связки Jest + Puppeteer, но т.к. Puppeteer рассчитан в основном на работу с Chromium, то в моем случае он не подходит и поэтому я решил попробовать быстро набирающий популярность Playwright, который изначально рассчитан на работу с различными браузерными движками. Разрабатывает этот фреймворк команда разработчиков из Microsoft, поэтому неудивительно, что он так быстро развивается и имеет хорошую поддержку.

На первый взгляд Playwright оказался достаточно стабильным, хорошо документированным и что важно, достаточно гибким. Фактически Playwright представляет собой готовое комплексное решение "всё-в-одном" для E2E-тестирования, где есть и тест-раннер (вместо Jest там используется свой) и взаимодействие с несколькими браузерными движками и полная поддержка TypeScript и всё это вместе работает без особой головной боли как единое целое. После Puppeteer и Jest многие вещи в API Playwright мне казались уже знакомыми (даже смешивание кода из разных JavaScript-окружений в одно, хотя в Playwright с этим полегче), что логично, ибо разработчики Playwright явно опирались на опыт других фреймворков.

Тем не менее, в своей задаче я столкнулся с некоторыми нюансами Playwright, которые приходилось учитывать:

Поскольку я веду разработку в Docker контейнерах, то все эти проблемы нивелируются за счет того, что Playwright может работать в клиент-серверном варианте и всю серверную часть можно вынести в отдельный контейнер используя официальный готовый базовый образ контейнера Playwright из Docker Hub (или собрав свой), который можно объединить вместе с проверяемым проектом в единый Docker Compose. При этом можно написать конфиги Playwright так, чтобы его можно было использовать и при клиент-серверном и при обычном варианте установки.

Пример организации E2E-тестирования с Playwright и Docker Compose

Обратите внимание, что в примерах идет речь о версии Playwright 1.52 и на момент прочтения статьи, актуальной может быть уже другая версия.

Со всеми описанными примерами можно ознакомиться в репозитории проекта, хотя допускается, что со временем там может что-то уже поменяться. В общем будьте внимательны.

Итак, у нас на руках в Docker Compose есть два контейнера, между которыми надо организовать правильное взаимодействие для успешного E2E-тестирования:

  1. Контейнер polyfill-pseudoclass-has — на котором сам проект и клиент Playwright.
  2. Контейнер playwright — представляет собой сервер Playwright со всеми возможными зависимостями. Для этого взят готовый Docker образ mcr.microsoft.com/playwright:v1.52.0-noble .

В клиент-серверном варианте Playwright сам обеспечивает всю передачу тестов с клиента на сервер и их выполнение, но вот для передачи тестируемых данных нам уже нужен отдельный веб-сервер, к которому будет обращаться сервер Playwright (из одноименного контейнера playwright).

Итого docker-compose.yml выглядит примерно так:

services:
  nodejs:
    container_name: polyfill-pseudoclass-has
    image: node:22-alpine
    working_dir: /home/node/app
    user: '1000:1000'
    restart: 'no'
    environment:
      - PW_TEST_CONNECT_WS_ENDPOINT=ws://playwright:3000/
      - SAMPLE_DEV_SERVER_PROTOCOL=http
      - SAMPLE_DEV_SERVER_DOMAIN=polyfill-pseudoclass-has
      - SAMPLE_DEV_SERVER_PORT=3000
      - SAMPLE_DEV_SERVER_PATH=/
    volumes:
      - ./:/home/node/app
    stdin_open: true
    tty: true

  playwright:
    container_name: playwright
    ports:
      - '3000:3000'
    init: true
    stdin_open: true
    tty: true
    working_dir: /home/pwuser
    user: pwuser
    image: mcr.microsoft.com/playwright:v1.52.0-noble
    command: /bin/sh -c "npx -y playwright@1.52.0 run-server --port 3000 --host 0.0.0.0"

Обратите внимание на переменные окружения, которые используются в основном контейнере:

Если очень упрощенно, то мы внутри Docker Compose можем обращаться к серверам, используя в качестве домена имена контейнеров. Сформированные по этому принципу адреса мы явно задаем в переменные окружения в docker-compose.yml, которые в свою очередь будут читаться в тестах и конфигах Playwright.

В основном проекте нужно установить сам клиент Playwright через npm install -D @playwright/test@1.52.0 , но без системных зависимостей в виде браузеров.

Для того чтобы удаленный сервер Playwright из одноименного контейнера playwright, смог получить тестовую страничку с полифилом, необходимо создать веб-сервер с этой самой тестовой страничкой. Поскольку я не хотел плодить лишние сущности и конфиги в проекте, я использовал в качестве веб-сервера возможности уже имеющегося в проекте Vite, а тестовую страничку разместил в в ./tests/samples/index.html относительно корня проекта.

Запуск веб-сервера задан в виде npm-скрипта в package.json:

{
  "scripts": {
    … ,
    "serve": "vite preview --host --port=3000 --outDir=./tests/samples"
  }
}

Далее нам надо как-то автоматически запускать веб-сервер во время тестов. Для этого в конфиге Playwright есть опция "webserver", которая отвечает за управление служебным веб-сервером. Эту опцию, а также несколько дополнительных мы в итоге добавим в defineConfig в playwright.config.js :

import {defineConfig, devices} from '@playwright/test';

const sampleWebServer = {
  protocol: process.env.SAMPLE_DEV_SERVER_PROTOCOL || 'http',
  domain: process.env.SAMPLE_DEV_SERVER_DOMAIN || 'localhost',
  port: process.env.SAMPLE_DEV_SERVER_PORT || '3000',
  path: process.env.SAMPLE_DEV_SERVER_PATH || '',
};

const sampleWebServerURI = new URL(
`${sampleWebServer.protocol}://${sampleWebServer.domain}:${sampleWebServer.port}${sampleWebServer.path}`
);

export default defineConfig({
  … ,
  testDir: './tests/e2e',
  use: {
    … ,
    baseURL: sampleWebServerURI.toString(),
  },
  webServer: {
    … ,
    command: 'npm run serve',
    url: sampleWebServerURI.toString(),
    ignoreHTTPSErrors: true,
  },
});

Скажу пару слов касательно этих опций в playwright.config.js:

Для удобства запуска E2E-тестов в package.json можно добавить npm-скрипт:

{
  "scripts": {
    … ,
    "test:e2e": "npx playwright test",
  }
}

В итоге получилось универсально:

Поскольку репозиторий проекта размещен на Github, то я решил воспользоваться его возможностями и добавить через Github Actions автоматическое E2E-тестирование при внесении изменений в репозиторий.

Как ни странно, но с этим никаких проблем не оказалось, поскольку еще при установке Playwright предлагает сгенерировать необходимые настройки Github Actions в виде стандартного конфига .github/workflows/playwright.yml , который в моем случае выглядел так:

name: Playwright Tests
on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    - name: Build library
      run: npm run build 
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

С учетом всех ранее описанных настроек, в этом конфиге ничего особо менять не пришлось и он сам по себе прекрасно работает.

Особенность адаптации полифила для возможности тестирования в современных версиях Playwright

Как я уже говорил, свежие версии Playwright официально совместимы только с относительно свежими версиями браузерных движков, а старый Playwright использовать совсем не вариант. В связи с этим встала проблема, проверки работоспособности полифила в браузерах, которые не поддерживают :has, имея в фреймворке только новые движки которые уже поддерживают этот самый :has.

Для такого случая в полифил пришлось добавить возможность переопределять ключевой идентификатор селектора :has, например на :_has (который точно нативно не поддерживается). Затем в тестах, выполнялись два одинаковых CSS-селектора с :has и :_has и общий результат в итоге проверялся на идентичность и в интегрированном в браузерное API варианте и в случае отдельно используемого API. По итогу получилось еще и исключить возможное влияние нативного :has.

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

Заключение

Вообще, кстати, в какой-то момент у меня появилось чувство, что в команде разработчиков Microsoft несколько лет назад от той существовавшей веб-разработки настолько устали, что аж выделили ресурсы на создание VSCode, TypeScript ну и в догонку ко всему этому еще и Playwright. Их понять можно.

Что касается тестирования полифила, то я прекрасно понимаю, что в примерах есть много спорных моментов и решений и что многие вещи стоило бы переработать. Допускаю, что опытные тестировщики многое сильно раскритикуют, причем обоснованно. Тем не менее это вполне работает и решает проблему даже в таком виде.

Что касается самого Playwright, то меня этот фреймворк реально порадовал, поскольку в нем есть всё для полноценного E2E-тестирования веб-приложений и ничего не надо дополнительно костылить (что редкость в современной веб-разработке!). Со временем конечно все может измениться, но пока что Playwright уверенно занял свое место в моей личной копилке используемых программных инструментов.

↑ ↓