Skip to main content
Version: v8

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:

// .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:

const config = {
chrome: {
headless: true,
desiredCapabilities: {
browserName: "chrome",
},
sessionsPerBrowser: 5, // 5 parallel Chrome sessions
waitTimeout: 10000,
},
};

and to 2:

const config = {
firefox: {
headless: true,
desiredCapabilities: {
browserName: "firefox",
},
sessionsPerBrowser: 2, // 2 parallel Firefox sessions
waitTimeout: 10000,
},
};

The value of the workers:

const config = {
system: {
workers: 1,
},
};

Next, the tests:

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:

const config = {
browsers: {
chrome: { sessionsPerBrowser: 3, testsPerSession: 1 },
firefox: { sessionsPerBrowser: 2, testsPerSession: 10 },
},
};

You’ll get the following result:

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:

npm install -D @testplane/chunks

And specify the parameters in the testplane.config.ts file:

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:

project/
├── .testplane.conf.js
├── package.json
└── tests/
├── registration.testplane.js # 500 tests
├── payment.testplane.js # 500 tests
└── auth.testplane.js # 500 tests
// 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
});
// 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
});
// 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:

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:

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

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

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:

// .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.