Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,371 @@
# Параллельный запуск тестов

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

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

### sessionsPerBrowser

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

```typescript
// .testplane.config.ts
export default {
browsers: {
chrome: {
sessionsPerBrowser: 5, // до 5 параллельных сессий Chrome
},
firefox: {
sessionsPerBrowser: 2, // до 2 параллельных сессий Firefox
},
},
};
```
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 и он может управляеть большим количеством браузеров одновременно. Главный параметр — это sessionsPerBrowser.

Диаграмму надо убрать или переработать.


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

### Воркеры

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

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

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

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

### testsPerSession

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

### Как это работает

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

```typescript
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", () => {
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
Тест 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

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

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

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

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

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

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

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

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

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

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

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

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

```typescript
describe("test examples", () => {
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();
});
});
```

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, 3 (≈ первая треть)
Чанк 1 → тесты 4, 5 (≈ вторая треть)
Чанк 2 → тесты 6, 7 (≈ третья треть)
```

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

```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
```

### Как плагин делит тесты

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

```css
┌──────────┬──────────┬──────────┐
│ Чанк 0 │ Чанк 1 │ Чанк 2 │
├──────────┼──────────┼──────────┤
│ тест 1 │ тест 4 │ тест 6 │
│ тест 2 │ тест 5 │ тест 7 │
│ тест 3 │ │ │
└──────────┴──────────┴──────────┘
```

:::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
```

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

#### Воркеры

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

#### sessionsPerBrowser

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

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