Skip to content
1 change: 1 addition & 0 deletions docs/basic-guides/parallelism.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Parallelism
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
# Параллельный запуск тестов

## Параллельность в Testplane

Параллельность в Testplane — это возможность одновременно выполнять несколько тестовых сценариев в разных браузерных сессиях, чтобы сократить общее время прогона тестов.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю здесь написать немного по-другому, т.к. это не возможность, которой можно пользоваться, а можно нет, а неотъемлемая часть продукта:

Testplane запускает тесты параллельно. Для этого запускается сразу несколько браузеров одновременно, которые управляются с помощью одного или нескольких воркеров. Параллельное выполнение тестов позволяет значительно сократить общее время прогона тестов.


### sessionsPerBrowser

В конфигурационном файле объявляются типы браузеров в поле `browsers`. Для каждого типа браузера независимо настраивается, сколько экземпляров может работать одновременно:

```typescript
// .testplane.config.ts
export default {
browsers: {
chrome: {
sessionsPerBrowser: 5, // до 5 параллельных сессий Chrome
},
firefox: {
sessionsPerBrowser: 2, // до 2 параллельных сессий Firefox
},
},
};
```

Параметр `sessionsPerBrowser` является ключевым в рамках управления параллельностью. Он определяет, сколько браузеров одного типа Testplane запустит одновременно. По умолчанию значение: `1`.

### Воркеры
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Остальные разделы названы параметрами. Предлагаю сделать консистентно, чтобы и тут тогда было workers.


Параметр `workers` определяет, сколько дочерних процессов-воркеров будет запущено. Архитектурно это выглядит так:

- `Master`-процесс — координирует запуск, формирует очереди тестов и раздает задания воркерам
- `Worker`-процессы — непосредственно исполняют тесты

Каждый воркер — это отдельный поток, но внутри одного воркера тесты выполняются конкурентно: когда тест ждет ответа от браузера (`await`), воркер не простаивает, а переключается на следующий тест. Поэтому даже один воркер способен обслуживать множество параллельных браузерных сессий.

:::warning Важно
`workers` и `sessionsPerBrowser` — независимые ограничения. Увеличение числа воркеров не увеличивает реальную параллельность, если `sessionsPerBrowser` остается прежним.
:::

### testsPerSession

Параметр `testsPerSession` отвечает за то, сколько тестов можно запускать последовательно в одной сессии браузера. Он ограничивает переиспользование сессии, чтобы не допустить падения тестов из-за деградации браузера, и не имеет отношения к параллельному запуску тестов.

### Как это работает
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю назвать секцию "пример"


В рамках примера параметру `sessionsPerBrowser` присвоены значения 5:

```typescript
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Форматирование в блоках кода съехало, т.к. не является валидным typescript кодом. Я ранее оставлял такой коммент, он актуален и тут:

У нас в проекте работает prettier, который умеет форматировать код внутри таких сниппетов, поддерживая единый стиль отступов, кавычек, запятых на конце и т.д.

Поэтому важно, чтобы если сниппет обозначен как ts, в нем был валидный код на TS. Сейчас это не так — и такой сниппет не будет форматироваться. Его можно поправить так, например:

const desiredCapabilities = {
  "goog:chromeOptions": {
      mobileEmulation: {
        deviceName: "iPhone 12 Pro",
    }
  }
};

либо как json, но главное, чтобы объявленный язык сниппета совпадал с реальностью.

chrome: {
headless: true,
desiredCapabilities: {
browserName: "chrome"
},
sessionsPerBrowser: 5, // 5 параллельных сессий Chrome
waitTimeout: 10000,
}
```

и 2:

```typescript
firefox: {
headless: true,
desiredCapabilities: {
browserName: "firefox"
},
sessionsPerBrowser: 2, // 2 параллельные сессии Firefox
waitTimeout: 10000,
}
```

Значение параметра `workers`:

```typescript
system: {
workers: 1,
},
```

Далее тесты:

```typescript
describe("test examples", () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот сниппет надо многократно сократить, можно полностью удалить тело тестов, оставив только названия, т.к. что происходит в самом тесте тут нам не важно

it("Поиск элемента по data-testid", async ({ browser }) => {
// Открываем страницу и ждем ее загрузки
await browser.openAndWait("https://testplane.io/");

// Ищем элемент по атрибуту data-testid
const element = await browser.$('[data-testid="main-content"]');

// Проверяем существование элемента в DOM
const isExisting = await element.isExisting();
console.log("Элемент с data-testid существует:", isExisting);
});

it("Поиск элемента на главной странице", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");

// Ищем элемент по классу "navbar"
const navbar = await browser.$(".navbar");

// Проверяем, отображается ли элемент на странице
const isDisplayed = await navbar.isDisplayed();
console.log("Навбар отображается:", isDisplayed);
});

it("Поиск элемента по id на главной странице", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");

// Ищем элемент по id "__docusaurus"
const main = await browser.$("#__docusaurus");

// Проверяем, отображается ли элемент на странице
const isDisplayed = await main.isDisplayed();
console.log("Элемент отображается:", isDisplayed);
});

it("Поиск элемента по типу атрибута", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");

// Ищем кнопку по атрибуту type="button"
// Формат селектора: element[type="value"]
const button = await browser.$('button[type="button"]');

// Проверяем существование элемента в DOM
const isExisting = await button.isExisting();
console.log("Кнопка существует:", isExisting);
});

it("Поиск элемента по тексту", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");

// Ищем элемент по тексту внутри него
const link = await browser.$('//a[text()="Docs"]');

// Проверяем существование элемента в DOM
const isExisting = await link.isExisting();
console.log("Элемент с текстом существует:", isExisting);
});

it("Поиск элемента по атрибуту", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");

// Ищем элемент по атрибуту type
const button = await browser.$('//button[@type="button"]');

// Проверяем существование элемента в DOM
const isExisting = await button.isExisting();
console.log("Элемент с атрибутом существует:", isExisting);
});

it("Поиск кнопки с помощью метода getByRole", async ({ browser }) => {
await browser.url("https://testplane.io/");

const button = await browser.getByRole("button", { name: "Get started" });

await button.click();
});
});
```

#### Как работает параллелизм в данном примере

Для Chrome открывается до 5 окон браузера одновременно. Каждое из них — это отдельная независимая сессия со своим `sessionId`:

```bash
Тест 1 → сессия chrome:abc123 → открывается окно Chrome #1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этой секции в целом пример классный, но давай объединим секции chrome+firefox и засинкаем названия с примером выше, чтобы получилось примерно так, имея конфиг:

browsers: {
    chrome:  { sessionsPerBrowser: 3, testsPerSession: 1 },
    firefox: { sessionsPerBrowser: 2, testsPerSession: 10 }
}

Получим такую последовательность:

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, затем закрываются

Тест 2 → сессия chrome:def456 → открывается окно Chrome #2
Тест 3 → сессия chrome:ghi789 → открывается окно Chrome #3
Тест 4 → сессия chrome:jkl012 → открывается окно Chrome #4
Тест 5 → сессия chrome:mno345 → открывается окно Chrome #5
```

Для Firefox логика та же, но одновременно работают только 2 окна. Это ограничение задано через `sessionsPerBrowser`:

```bash
Тест 1 → сессия firefox:fb59f7 → открывается окно Firefox #1
Тест 2 → сессия firefox:660ee0 → открывается окно Firefox #2
Тест 3 → ждёт в очереди...
как только тест 1 завершился → сессия fb59f7 освободилась
Тест 3 → сессия firefox:fb59f7 → то же окно Firefox #1, новый тест
```

:::warning Важно
`workers` управляет количеством `Node.js`-процессов, а `sessionsPerBrowser` — количеством одновременных браузерных сессий внутри каждого воркера. При `workers`: `1` все 7 сессий управляются одним процессом.
:::

## Шардирование

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

### Плагин testplane-chunks
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Несоответствие — здесь testplane-chunks, далее @testplane/chunks. Правильно использовать везде @testplane/chunks


Плагин `@testplane/chunks` позволяет распараллелить запуск тестов на нескольких серверах, тем самым ускорив процесс. Однако сам плагин не занимается какой-либо оркестрацией, распараллеливанием запуска или слиянием получившихся отдельных отчетов в один итоговый отчет.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А чем же занимается? =)
Можно сразу в двух словах дописать после этого "Плагин разбивает набор тестов проекта на детерминированные куски, упрощая запуск нужной части."


#### Установка и подключение

Для установки выполните команду:

```bash
npm install -D @testplane/chunks
```

И укажите параметры в файле `testplane.config.ts`.

```typescript
module.exports = {
plugins: {
"@testplane/chunks": {
count: 7, // Разбить тесты на 7 порций (чанков)
run: 1, // Запустить первую порцию
},

// другие плагины Testplane...
},

// другие настройки Testplane...
};
```

#### Расшифровка параметров конфигурации
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю можно убрать секцию


| Параметр | Тип | По умолчанию | Описание |
| -------- | -------- | ------------ | ----------------------------------------------------------------- |
| `count` | `Number` | `1` | Количество порций (чанков), на которые нужно разбить набор тестов |
| `run` | `Number` | `1` | Номер чанка, тесты из которого нужно запустить |

### Как разбить запуск тестов

В качестве примера будет использован следующий набор тестов:

```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
});
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут надо рассказать или сослаться на то, как объединить отчеты этих чанков в 1 с помощью команды merge-reports html-reporter.

Резделение тестов на чанки:

```css
Чанк 0 → тесты 1, 2, ..., 500 (≈ первая треть)
Чанк 1 → тесты 1, 2, ..., 500 (≈ вторая треть)
Чанк 2 → тесты 1, 2, ..., 500 (≈ третья треть)
```

Запуск каждого чанка отдельно через терминал:

```bash
# Терминал 1 — Чанк 0
CHUNKS_COUNT=3 CHUNKS_CURRENT=0 npx testplane

# Терминал 2 — Чанк 1
CHUNKS_COUNT=3 CHUNKS_CURRENT=1 npx testplane

# Терминал 2 — Чанк 2
CHUNKS_COUNT=3 CHUNKS_CURRENT=2 npx testplane
```

### Как плагин делит тесты
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю это можно убрать.


После сортировки по `fullTitle` набор тестов делится на 3 чанка:

```css
┌──────────┬──────────┬──────────┐
│ Чанк 0 │ Чанк 1 │ Чанк 2 │
├──────────┼──────────┼──────────┤
│ тест 1 │ тест 1 │ тест 1 │
│ тест 2 │ тест 2 │ тест 2 │
│ ... │ ... │ ... │
│ тест 500 │ тест 500 │ тест 500 │
└──────────┴──────────┴──────────┘
```

:::tip Примечание
Главная идея чанков: если `workers` распределяет тесты внутри одного процесса, то чанки делят тесты между несколькими независимыми процессами — например, на разных `CI`-агентах или на одной машине через `concurrently`.
:::

### Как объединить отчеты

Чтобы объединить несколько отчетов в один, используйте команду `merge-reports`. Она принимает пути к директориям с отчетами, файлам баз данных или к файлам `databaseUrls.json`, после чего создает новый html-отчет в папке назначения с данными из всех переданных отчетов.

Пример использования:

```bash
npx html-reporter merge-reports report-dir/ path-to-database.db path-to-databaseUrls.json -d dest-report -h foo=bar
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай упростим пример:

npx html-reporter merge-reports report-chunk-1/ report-chunk-2/ report-chunk-3/ -d merged-report

```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай здесь все таки добавим секцию "пример github actions".

В ней кратко скажем:

Ниже показан пример запуска @testplane/chunks в GitHub Actions. Тесты разбиваются на 3 чанка, каждый чанк выполняется в отдельном matrix job, а затем отчёты всех чанков скачиваются из S3 и объединяются в один итоговый отчёт.

И под кат занесем полный пример:

Code
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

## Рекомендуемые настройки и их расчет

#### Воркеры
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь тоже workers


Рекомендуемое значение — 8. Любое другое не должно превышать количество ядер CPU.

#### sessionsPerBrowser

При локальном запуске значение не превышает 5. В ином случает примерное значение можно вычислить путем деления доступных ресурсов на требуемое для конкретного браузера. При расчете помните о затратах на запуск самих тестов.

#### Количество чанков

При расчете количества чанков необходимо отталкиваться от:

- количества доступных браузеров
- оверхеда на создание чанка
- желаемого общего времени прогона

Рекомендация — не меньше 500 тестов на чанк.
Loading