diff --git a/docs/basic-guides/parallelism.mdx b/docs/basic-guides/parallelism.mdx new file mode 100644 index 0000000..d6c6bc2 --- /dev/null +++ b/docs/basic-guides/parallelism.mdx @@ -0,0 +1,407 @@ +# Parallelism + +## Parallelism in Testplane + +Testplane runs tests in parallel by launching multiple browsers simultaneously, which are managed by one or several workers. Parallel test execution significantly reduces the overall test run time. + +### sessionsPerBrowser + +In the configuration file, browser types are declared in the `browsers` field. For each browser type, you can independently configure how many instances can run simultaneously: + +```typescript +// .testplane.config.ts +export default { + browsers: { + chrome: { + sessionsPerBrowser: 5, // up to 5 parallel Chrome sessions + }, + firefox: { + sessionsPerBrowser: 2, // up to 2 parallel Firefox sessions + }, + }, +}; +``` + +The `sessionsPerBrowser` parameter is key for managing parallelism. It determines how many browsers of the same type Testplane will launch simultaneously. The default value is `1`. + +### Workers + +The workers parameter determines how many child worker processes will be launched. Architecturally, it works as follows: + +- Master process — coordinates the launch, forms test queues and assigns tasks to workers; +- Worker processes — execute the tests directly. + +Each worker is a separate thread, but within a single worker, tests run concurrently: when a test is waiting for a response from the browser (`await`), the worker doesn’t stay idle — it switches to the next test. Therefore, even a single worker can handle multiple parallel browser sessions. + +:::warning Warning +`workers` and `sessionsPerBrowser` are independent limits. Increasing the number of workers won’t boost actual concurrency if `sessionsPerBrowser` remains unchanged. +::: + +### testsPerSession + +The `testsPerSession` parameter determines how many tests can be run sequentially in a single browser session. It limits session reuse to prevent test failures caused by browser degradation, and it’s not related to parallel test execution. + +### How it works + +In this example, the `sessionsPerBrowser` parameter is set to 5: + +```typescript +const config = { + chrome: { + headless: true, + desiredCapabilities: { + browserName: "chrome", + }, + sessionsPerBrowser: 5, // 5 parallel Chrome sessions + waitTimeout: 10000, + }, +}; +``` + +and to 2: + +```typescript +const config = { + firefox: { + headless: true, + desiredCapabilities: { + browserName: "firefox", + }, + sessionsPerBrowser: 2, // 2 parallel Firefox sessions + waitTimeout: 10000, + }, +}; +``` + +The value of the `workers`: + +```typescript +const config = { + system: { + workers: 1, + }, +}; +``` + +Next, the tests: + +```typescript +describe("test examples", () => { + it("Find an element by data‑testid", async ({ browser }) => { + // test body + }); + + it("Find an element on the main page", async ({ browser }) => { + // test body + }); + + it("Find an element by ID on the main page", async ({ browser }) => { + // test body + }); + + it("Find an element by attribute type", async ({ browser }) => { + // test body + }); + + it("Find an element by text", async ({ browser }) => { + // test body + }); + + it("Find an element by attribute", async ({ browser }) => { + // test body + }); + + it("Find a button using the getByRole method", async ({ browser }) => { + // test body + }); +}); +``` + +#### How parallelism works in this example + +For Chrome and Firefox, up to 5 and 2 browser windows are opened simultaneously, respectively. Each of them is a separate independent session with its own `sessionId`. + +With the following parameter values: + +```typescript +const config = { + browsers: { + chrome: { sessionsPerBrowser: 3, testsPerSession: 1 }, + firefox: { sessionsPerBrowser: 2, testsPerSession: 10 }, + }, +}; +``` + +You’ll get the following result: + +```json +CH-* — Chrome sessions +FF-* — Firefox sessions +[n/m] = n out of m slots are occupied + +Step 1 + chrome [3/3]: windows CH‑1, CH‑2, CH‑3 are opened → run test1, test2, test3 + firefox [2/2]: windows FF‑1, FF‑2 are opened → run test1, test2 + +Step 2 + chrome [3/3]: CH‑1 completes test1 and closes; CH‑4 is created → test4 is launched + firefox [2/2]: FF‑1 completes test1 and is reused → test3 is launched + +Step 3 + chrome [2/3]: CH‑2 completes test2 and closes + firefox [2/2]: FF‑2 completes test2 and is reused → test4 is launched + +Step 4 + chrome [0/3]: CH‑3 and CH‑4 complete test3 and test4, then close + firefox [0/2]: FF‑1 and FF‑2 complete test3 and test4, then close +``` + +:::warning Warning +`workers` controls the number of `Node.js` processes, while `sessionsPerBrowser` controls the number of concurrent browser sessions within each worker. With `workers: 1`, all 7 sessions are managed by a single process. +::: + +## Sharding + +When you have thousands of tests, a single run can take an unacceptably long time — even with maximum concurrency. In such cases, sharding is used: the entire test suite is split into several independent parts (chunks) that are run in parallel on different machines or in separate `CI`-jobs. + +### @testplane/chunks plugin + +The plugin splits the project’s test suite into deterministic fragments, making it easier to run a specific part. + +#### Installation and setup + +To install, run the following command: + +```bash +npm install -D @testplane/chunks +``` + +And specify the parameters in the `testplane.config.ts` file: + +```typescript +module.exports = { + plugins: { + "@testplane/chunks": { + count: 7, // Split tests into 7 chunks + run: 1, // Run the first chunk + }, + + // other Testplane plugins... + }, + + // other Testplane settings... +}; +``` + +As an example, the following test suite will be used: + +```text +project/ +├── .testplane.conf.js +├── package.json +└── tests/ + ├── registration.testplane.js # 500 tests + ├── payment.testplane.js # 500 tests + └── auth.testplane.js # 500 tests +``` + +```javascript +// registration.testplane.js + +describe("Registration", () => { + it("test 1 - empty email shows an error", async ({ browser }) => {}); + it("test 2 - invalid email format", async ({ browser }) => {}); + it("test 3 - empty password shows an error", async ({ browser }) => {}); + + // ... + + // test 500 +}); +``` + +```javascript +// payment.testplane.js + +describe("Payment", () => { + it("test 1 - empty card number shows an error", async ({ browser }) => {}); + it("test 2 - invalid card number", async ({ browser }) => {}); + it("test 3 - expired card", async ({ browser }) => {}); + + // ... + + // test 500 +}); +``` + +```javascript +// auth.testplane.js + +describe("Authorization", () => { + it("test 1 - empty email shows an error", async ({ browser }) => {}); + it("test 2 - empty password shows an error", async ({ browser }) => {}); + it("test 3 - incorrect password shows an error", async ({ browser }) => {}); + + // ... + + // test 500 +}); +``` + +Splitting tests into chunks: + +```css +Chunk 0 → tests 1, 2, ..., 500 (≈ first part) +Chunk 1 → tests 1, 2, ..., 500 (≈ second part) +Chunk 2 → tests 1, 2, ..., 500 (≈ third part) +``` + +Running each chunk separately via the terminal using environment variables or command‑line arguments: + +```bash +testplane_chunks_count=3 testplane_chunks_run=0 npx testplane + +testplane_chunks_count=3 testplane_chunks_run=1 npx testplane + +testplane_chunks_count=3 testplane_chunks_run=2 npx testplane +``` + +```bash +npx testplane --chunks-count 3 --chunks-run 0 + +npx testplane --chunks-count 3 --chunks-run 1 + +npx testplane --chunks-count 3 --chunks-run 2 +``` + +### How to merge reports + +To combine several reports into one, use the `merge-reports` command. It accepts paths to report directories, database files, or `databaseUrls.json` files, and then creates a new html-report in the destination folder with data from all the provided reports. + +Example of usage: + +```bash +npx html-reporter merge-reports report-chunk-1/ report-chunk-2/ report-chunk-3/ -d merged-report +``` + +### GitHub Actions example + +Below is an example of running `@testplane/chunks` in GitHub Actions. The tests are split into 3 chunks — each chunk is executed in a separate `matrix job`. Then, the reports from all chunks are downloaded from `S3` and merged into a single final report. + +
+Code + +```yaml +name: Testplane + +on: + pull_request: + +permissions: + id-token: write + contents: read + +jobs: + chunks: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - run: npx testplane --chunks-count 3 --chunks-run ${{ matrix.chunk }} + + - uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: eu-central-1 + + - uses: jakejarvis/s3-sync-action@v0.5.1 + with: + args: --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.S3_BUCKET }} + AWS_REGION: eu-central-1 + SOURCE_DIR: testplane-report + DEST_DIR: testplane/${{ github.run_id }}/chunk-${{ matrix.chunk }} + + merge-report: + runs-on: ubuntu-latest + needs: chunks + if: ${{ always() }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: eu-central-1 + + - run: | + mkdir -p reports/chunk-1 reports/chunk-2 reports/chunk-3 + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-1/ reports/chunk-1 --recursive + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-2/ reports/chunk-2 --recursive + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-3/ reports/chunk-3 --recursive + + - run: | + npx testplane merge-reports \ + reports/chunk-1 \ + reports/chunk-2 \ + reports/chunk-3 \ + --output merged-report + + - uses: jakejarvis/s3-sync-action@v0.5.1 + with: + args: --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.S3_BUCKET }} + AWS_REGION: eu-central-1 + SOURCE_DIR: merged-report + DEST_DIR: testplane/${{ github.run_id }}/merged +``` + +
+ +## Recommended settings and their calculation + +### Workers + +The recommended value is 8. Any other value should not exceed the number of CPU cores. + +### sessionsPerBrowser + +Base the value on your hardware capabilities — for debugging or running individual tests, this value typically falls within the 1–5 range. When using local browsers in CI/Docker images, rely on a rough estimate: one `LinuxDesktop`-browser session consumes 1.2 CPU cores. If you’re using a remote grid, the provider usually specifies the number of available slots — for example, 400. Next, calculate the number of concurrent task runs in CI — say, up to 10. In this case, you can set the total `sessionsPerBrowser` value in the CI config: + +```typescript +// .testplane.config.ts +export default { + browsers: { + chrome: { + sessionsPerBrowser: 40, + }, + }, +}; +``` + +### Number of chunks + +When calculating the number of chunks, you should take the following factors into account: + +- the number of available browsers; +- the overhead for creating a chunk; +- the desired total run time. + +Recommendation: aim for at least 500 tests per chunk. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/parallelism.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/parallelism.mdx new file mode 100644 index 0000000..2febcdf --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/parallelism.mdx @@ -0,0 +1,409 @@ +# Параллельный запуск тестов + +## Параллельность в Testplane + +Testplane запускает тесты параллельно с помощью запуска нескольких браузеров одновременно, которые управляются одним или нексколькими воркерами. Параллельное выполнение тестов позволяет значительно сократить общее время прогона тестов. + +### sessionsPerBrowser + +В конфигурационном файле объявляются типы браузеров в поле `browsers`. Для каждого типа браузера независимо настраивается, сколько экземпляров может работать одновременно: + +```typescript +// .testplane.config.ts +export default { + browsers: { + chrome: { + sessionsPerBrowser: 5, // до 5 параллельных сессий Chrome + }, + firefox: { + sessionsPerBrowser: 2, // до 2 параллельных сессий Firefox + }, + }, +}; +``` + +Параметр `sessionsPerBrowser` является ключевым в рамках управления параллельностью. Он определяет, сколько браузеров одного типа Testplane запустит одновременно. По умолчанию значение: `1`. + +### Workers + +Параметр `workers` определяет, сколько дочерних процессов-воркеров будет запущено. Архитектурно это выглядит так: + +- `Master`-процесс — координирует запуск, формирует очереди тестов и раздает задания воркерам +- `Worker`-процессы — непосредственно исполняют тесты + +Каждый воркер — это отдельный поток, но внутри одного воркера тесты выполняются конкурентно: когда тест ждет ответа от браузера (`await`), воркер не простаивает, а переключается на следующий тест. Поэтому даже один воркер способен обслуживать множество параллельных браузерных сессий. + +:::warning Важно +`workers` и `sessionsPerBrowser` — независимые ограничения. Увеличение числа воркеров не увеличивает реальную параллельность, если `sessionsPerBrowser` остается прежним. +::: + +### testsPerSession + +Параметр `testsPerSession` отвечает за то, сколько тестов можно запускать последовательно в одной сессии браузера. Он ограничивает переиспользование сессии, чтобы не допустить падения тестов из-за деградации браузера, и не имеет отношения к параллельному запуску тестов. + +### Пример + +В рамках примера параметру `sessionsPerBrowser` присвоены значения 5: + +```typescript +const config = { + chrome: { + headless: true, + desiredCapabilities: { + browserName: "chrome", + }, + sessionsPerBrowser: 5, // 5 параллельных сессий Chrome + waitTimeout: 10000, + }, +}; +``` + +и 2: + +```typescript +const config = { + firefox: { + headless: true, + desiredCapabilities: { + browserName: "firefox", + }, + sessionsPerBrowser: 2, // 2 параллельные сессии Firefox + waitTimeout: 10000, + }, +}; +``` + +Значение параметра `workers`: + +```typescript +const config = { + system: { + workers: 1, + }, +}; +``` + +Далее тесты: + +```typescript +describe("test examples", () => { + it("Поиск элемента по data-testid", async ({ browser }) => { + // тело теста + }); + + it("Поиск элемента на главной странице", async ({ browser }) => { + // тело теста + }); + + it("Поиск элемента по id на главной странице", async ({ browser }) => { + // тело теста + }); + + it("Поиск элемента по типу атрибута", async ({ browser }) => { + // тело теста + }); + + it("Поиск элемента по тексту", async ({ browser }) => { + // тело теста + }); + + it("Поиск элемента по атрибуту", async ({ browser }) => { + // тело теста + }); + + it("Поиск кнопки с помощью метода getByRole", async ({ browser }) => { + // тело теста + }); +}); +``` + +#### Как работает параллелизм в данном примере + +Для Chrome и Firefox открывается до 5 и 2 окон браузера одновременно. Каждое из них — это отдельная независимая сессия со своим `sessionId`. + +При таких значениях параметров: + +```typescript +const config = { + browsers: { + chrome: { sessionsPerBrowser: 3, testsPerSession: 1 }, + firefox: { sessionsPerBrowser: 2, testsPerSession: 10 }, + }, +}; +``` + +Вы получите следующий результат: + +```json +CH-* — сессии chrome +FF-* — сессии firefox +[n/m] = занято n из m слотов + +Шаг 1 + chrome [3/3]: открываются окна CH-1, CH-2, CH-3 → запускают test1, test2, test3 + firefox [2/2]: открываются окна FF-1, FF-2 → запускают test1, test2 + +Шаг 2 + chrome [3/3]: CH-1 завершает test1 и закрывается; создается CH-4 → запускается test4 + firefox [2/2]: FF-1 завершает test1 и переиспользуется → запускается test3 + +Шаг 3 + chrome [2/3]: CH-2 завершает test2 и закрывается + firefox [2/2]: FF-2 завершает test2 и переиспользуется → запускается test4 + +Шаг 4 + chrome [0/3]: CH-3 и CH-4 завершают test3 и test4, затем закрываются + firefox [0/2]: FF-1 и FF-2 завершают test3 и test4, затем закрываются +``` + +:::warning Важно +`workers` управляет количеством `Node.js`-процессов, а `sessionsPerBrowser` — количеством одновременных браузерных сессий внутри каждого воркера. При `workers`: `1` все 7 сессий управляются одним процессом. +::: + +## Шардирование + +При наличии тысяч тестов время одного запуска может быть неприемлемо большим даже при максимальном параллелизме. В таких случаях используют шардирование — разбивку всего набора тестов на несколько независимых частей (чанков), которые запускаются параллельно на разных машинах или в разных `CI`-джобах. + +### Плагин @testplane/chunks + +Плагин разбивает набор тестов проекта на детерминированные фрагменты, упрощая запуск нужной части. + +#### Установка и подключение + +Для установки выполните команду: + +```bash +npm install -D @testplane/chunks +``` + +И укажите параметры в файле `testplane.config.ts`. + +```typescript +module.exports = { + plugins: { + "@testplane/chunks": { + count: 7, // Разбить тесты на 7 порций (чанков) + run: 1, // Запустить первую порцию + }, + + // другие плагины Testplane... + }, + + // другие настройки Testplane... +}; +``` + +### Как разбить запуск тестов + +В качестве примера будет использован следующий набор тестов: + +```text +project/ +├── .testplane.conf.js +├── package.json +└── tests/ + ├── registration.testplane.js # 500 тестов + ├── payment.testplane.js # 500 тестов + └── auth.testplane.js # 500 тестов +``` + +```javascript +//registration.testplane.js + +describe("Регистрация", () => { + it("тест 1 - пустой email показывает ошибку", async ({ browser }) => {}); + it("тест 2 - невалидный формат email", async ({ browser }) => {}); + it("тест 3 - пустой пароль показывает ошибку", async ({ browser }) => {}); + + // ... + + // тест 500 +}); +``` + +```javascript +//payment.testplane.js + +describe("Оплата", () => { + it("тест 1 - пустой номер карты показывает ошибку", async ({ browser }) => {}); + it("тест 2 - невалидный номер карты", async ({ browser }) => {}); + it("тест 3 - истекший срок действия карты", async ({ browser }) => {}); + + // ... + + // тест 500 +}); +``` + +```javascript +//auth.testplane.js + +describe("Авторизация", () => { + it("тест 1 - пустой email показывает ошибку", async ({ browser }) => {}); + it("тест 2 - пустой пароль показывает ошибку", async ({ browser }) => {}); + it("тест 3 - неверный пароль показывает ошибку", async ({ browser }) => {}); + + // ... + + // тест 500 +}); +``` + +Резделение тестов на чанки: + +```css +Чанк 0 → тесты 1, 2, ..., 500 (≈ первая треть) +Чанк 1 → тесты 1, 2, ..., 500 (≈ вторая треть) +Чанк 2 → тесты 1, 2, ..., 500 (≈ третья треть) +``` + +Запуск каждого чанка отдельно через терминал с помощью переменных окружения или аргументов командной строки: + +```bash +testplane_chunks_count=3 testplane_chunks_run=0 npx testplane + +testplane_chunks_count=3 testplane_chunks_run=1 npx testplane + +testplane_chunks_count=3 testplane_chunks_run=2 npx testplane +``` + +```bash +npx testplane --chunks-count 3 --chunks-run 0 + +npx testplane --chunks-count 3 --chunks-run 1 + +npx testplane --chunks-count 3 --chunks-run 2 +``` + +### Как объединить отчеты + +Чтобы объединить несколько отчетов в один, используйте команду `merge-reports`. Она принимает пути к директориям с отчетами, файлам баз данных или к файлам `databaseUrls.json`, после чего создает новый html-отчет в папке назначения с данными из всех переданных отчетов. + +Пример использования: + +```bash +npx html-reporter merge-reports report-chunk-1/ report-chunk-2/ report-chunk-3/ -d merged-report +``` + +### Пример GitHub Actions + +Ниже показан пример запуска `@testplane/chunks` в GitHub Actions. Тесты разбиваются на 3 чанка, каждый чанк выполняется в отдельном `matrix job`, а затем отчеты всех чанков скачиваются из `S3` и объединяются в один итоговый отчет. + +
+Полный пример + +```yaml +name: Testplane + +on: + pull_request: + +permissions: + id-token: write + contents: read + +jobs: + chunks: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - run: npx testplane --chunks-count 3 --chunks-run ${{ matrix.chunk }} + + - uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: eu-central-1 + + - uses: jakejarvis/s3-sync-action@v0.5.1 + with: + args: --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.S3_BUCKET }} + AWS_REGION: eu-central-1 + SOURCE_DIR: testplane-report + DEST_DIR: testplane/${{ github.run_id }}/chunk-${{ matrix.chunk }} + + merge-report: + runs-on: ubuntu-latest + needs: chunks + if: ${{ always() }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: eu-central-1 + + - run: | + mkdir -p reports/chunk-1 reports/chunk-2 reports/chunk-3 + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-1/ reports/chunk-1 --recursive + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-2/ reports/chunk-2 --recursive + aws s3 cp s3://${{ secrets.S3_BUCKET }}/testplane/${{ github.run_id }}/chunk-3/ reports/chunk-3 --recursive + + - run: | + npx testplane merge-reports \ + reports/chunk-1 \ + reports/chunk-2 \ + reports/chunk-3 \ + --output merged-report + + - uses: jakejarvis/s3-sync-action@v0.5.1 + with: + args: --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.S3_BUCKET }} + AWS_REGION: eu-central-1 + SOURCE_DIR: merged-report + DEST_DIR: testplane/${{ github.run_id }}/merged +``` + +
+ +## Рекомендуемые настройки и их расчет + +#### Workers + +Рекомендуемое значение — 8. Любое другое не должно превышать количество ядер CPU. + +#### sessionsPerBrowser + +Отталкивайтесь от мощностей своего оборудования, для отладки/запуска единичных тестов это значение обычно находится в диапазоне 1-5. При использовании локальных браузеров в CI/Docker-образах отталкивайтесь от примерной оценки: одна сессия `LinuxDesktop`-браузера потребляет 1.2 ядра CPU. Если вы используете удаленный грид, обычно сам провайдер сообщает количество доступных слотов — например, 400. Далее рассчитайте количество одновременно запущенных прогонов задач в CI, например, до 10. Тогда в конфиге для CI можно указать суммарное значение `sessionsPerBrowser`: + +```typescript +// .testplane.config.ts +export default { + browsers: { + chrome: { + sessionsPerBrowser: 40, + }, + }, +}; +``` + +#### Количество чанков + +При расчете количества чанков необходимо отталкиваться от: + +- количества доступных браузеров +- оверхеда на создание чанка +- желаемого общего времени прогона + +Рекомендация — не меньше 500 тестов на чанк.