feat(test runner): workers as percentage (#17400)
Allows to set workers as a percentage of logical CPUs, for example
"50%".
Examples :
```bash
npx playwright test --workers 3
npx playwright test --workers 50%
```
```js
const config: PlaywrightTestConfig = {
// ...
workers: '33%',
}
```
This commit is contained in:
parent
d2300674ef
commit
a15fe50e7b
|
|
@ -791,13 +791,13 @@ module.exports = config;
|
||||||
|
|
||||||
## property: TestConfig.workers
|
## property: TestConfig.workers
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[int]>
|
- type: ?<[int]|[string]>
|
||||||
|
|
||||||
The maximum number of concurrent worker processes to use for parallelizing tests.
|
The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of logical CPU cores, e.g. `'50%'.`
|
||||||
|
|
||||||
Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to speed up test execution.
|
Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to speed up test execution.
|
||||||
|
|
||||||
Defaults to one half of the number of CPU cores. Learn more about [parallelism and sharding](../test-parallel.md) with Playwright Test.
|
Defaults to half of the number of logical CPU cores. Learn more about [parallelism and sharding](../test-parallel.md) with Playwright Test.
|
||||||
|
|
||||||
```js tab=js-js
|
```js tab=js-js
|
||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
|
|
|
||||||
|
|
@ -516,7 +516,7 @@ In addition to configuring [Browser] or [BrowserContext], videos or screenshots,
|
||||||
- `testMatch`: Glob patterns or regular expressions that match test files. For example, `'**/todo-tests/*.spec.ts'`. By default, Playwright Test runs `.*(test|spec)\.(js|ts|mjs)` files.
|
- `testMatch`: Glob patterns or regular expressions that match test files. For example, `'**/todo-tests/*.spec.ts'`. By default, Playwright Test runs `.*(test|spec)\.(js|ts|mjs)` files.
|
||||||
- `timeout`: Time in milliseconds given to each test. Learn more about [various timeouts](./test-timeouts.md).
|
- `timeout`: Time in milliseconds given to each test. Learn more about [various timeouts](./test-timeouts.md).
|
||||||
- `webServer: { command: string, port?: number, url?: string, ignoreHTTPSErrors?: boolean, timeout?: number, reuseExistingServer?: boolean, cwd?: string, env?: object }` - Launch a process and wait that it's ready before the tests will start. See [launch web server](./test-advanced.md#launching-a-development-web-server-during-the-tests) configuration for examples.
|
- `webServer: { command: string, port?: number, url?: string, ignoreHTTPSErrors?: boolean, timeout?: number, reuseExistingServer?: boolean, cwd?: string, env?: object }` - Launch a process and wait that it's ready before the tests will start. See [launch web server](./test-advanced.md#launching-a-development-web-server-during-the-tests) configuration for examples.
|
||||||
- `workers`: The maximum number of concurrent worker processes to use for parallelizing tests.
|
- `workers`: The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of logical CPU cores, e.g. `'50%'.`
|
||||||
|
|
||||||
You can specify these options in the configuration file. Note that testing options are **top-level**, do not put them into the `use` section.
|
You can specify these options in the configuration file. Note that testing options are **top-level**, do not put them into the `use` section.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ function addTestCommand(program: Command) {
|
||||||
command.option('-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`);
|
command.option('-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`);
|
||||||
command.option('--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`);
|
command.option('--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`);
|
||||||
command.option('--ignore-snapshots', `Ignore screenshot and snapshot expectations`);
|
command.option('--ignore-snapshots', `Ignore screenshot and snapshot expectations`);
|
||||||
command.option('-j, --workers <workers>', `Number of concurrent workers, use 1 to run in a single worker (default: number of CPU cores / 2)`);
|
command.option('-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`);
|
||||||
command.option('--list', `Collect all the tests and report them, but do not run`);
|
command.option('--list', `Collect all the tests and report them, but do not run`);
|
||||||
command.option('--max-failures <N>', `Stop after the first N failures`);
|
command.option('--max-failures <N>', `Stop after the first N failures`);
|
||||||
command.option('--output <dir>', `Folder for output artifacts (default: "test-results")`);
|
command.option('--output <dir>', `Folder for output artifacts (default: "test-results")`);
|
||||||
|
|
@ -272,7 +272,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
|
||||||
timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
|
timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
|
||||||
ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined,
|
ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined,
|
||||||
updateSnapshots: options.updateSnapshots ? 'all' as const : undefined,
|
updateSnapshots: options.updateSnapshots ? 'all' as const : undefined,
|
||||||
workers: options.workers ? parseInt(options.workers, 10) : undefined,
|
workers: options.workers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import type { Reporter } from '../types/testReporter';
|
||||||
import { builtInReporters } from './runner';
|
import { builtInReporters } from './runner';
|
||||||
import { isRegExp, calculateSha1 } from 'playwright-core/lib/utils';
|
import { isRegExp, calculateSha1 } from 'playwright-core/lib/utils';
|
||||||
import { serializeError } from './util';
|
import { serializeError } from './util';
|
||||||
import { hostPlatform } from 'playwright-core/lib/utils/hostPlatform';
|
|
||||||
import { FixturePool, isFixtureOption } from './fixtures';
|
import { FixturePool, isFixtureOption } from './fixtures';
|
||||||
import type { TestTypeImpl } from './testType';
|
import type { TestTypeImpl } from './testType';
|
||||||
|
|
||||||
|
|
@ -143,7 +142,19 @@ export class Loader {
|
||||||
this._fullConfig.shard = takeFirst(config.shard, baseFullConfig.shard);
|
this._fullConfig.shard = takeFirst(config.shard, baseFullConfig.shard);
|
||||||
this._fullConfig._ignoreSnapshots = takeFirst(config.ignoreSnapshots, baseFullConfig._ignoreSnapshots);
|
this._fullConfig._ignoreSnapshots = takeFirst(config.ignoreSnapshots, baseFullConfig._ignoreSnapshots);
|
||||||
this._fullConfig.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots);
|
this._fullConfig.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots);
|
||||||
this._fullConfig.workers = takeFirst(config.workers, baseFullConfig.workers);
|
|
||||||
|
const workers = takeFirst(config.workers, '50%');
|
||||||
|
if (typeof workers === 'string') {
|
||||||
|
if (workers.endsWith('%')) {
|
||||||
|
const cpus = os.cpus().length;
|
||||||
|
this._fullConfig.workers = Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
|
||||||
|
} else {
|
||||||
|
this._fullConfig.workers = parseInt(workers, 10);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._fullConfig.workers = workers;
|
||||||
|
}
|
||||||
|
|
||||||
const webServers = takeFirst(config.webServer, baseFullConfig.webServer);
|
const webServers = takeFirst(config.webServer, baseFullConfig.webServer);
|
||||||
if (Array.isArray(webServers)) { // multiple web server mode
|
if (Array.isArray(webServers)) { // multiple web server mode
|
||||||
// Due to previous choices, this value shows up to the user in globalSetup as part of FullConfig. Arrays are not supported by the old type.
|
// Due to previous choices, this value shows up to the user in globalSetup as part of FullConfig. Arrays are not supported by the old type.
|
||||||
|
|
@ -572,8 +583,10 @@ function validateConfig(file: string, config: Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('workers' in config && config.workers !== undefined) {
|
if ('workers' in config && config.workers !== undefined) {
|
||||||
if (typeof config.workers !== 'number' || config.workers <= 0)
|
if (typeof config.workers === 'number' && config.workers <= 0)
|
||||||
throw errorWithFile(file, `config.workers must be a positive number`);
|
throw errorWithFile(file, `config.workers must be a positive number`);
|
||||||
|
else if (typeof config.workers === 'string' && !config.workers.endsWith('%'))
|
||||||
|
throw errorWithFile(file, `config.workers must be a number or percentage`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -631,9 +644,6 @@ function validateProject(file: string, project: Project, title: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cpus = os.cpus().length;
|
|
||||||
const workers = hostPlatform.startsWith('mac') && hostPlatform.endsWith('arm64') ? cpus : Math.ceil(cpus / 2);
|
|
||||||
|
|
||||||
export const baseFullConfig: FullConfigInternal = {
|
export const baseFullConfig: FullConfigInternal = {
|
||||||
forbidOnly: false,
|
forbidOnly: false,
|
||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
|
|
@ -654,7 +664,7 @@ export const baseFullConfig: FullConfigInternal = {
|
||||||
shard: null,
|
shard: null,
|
||||||
updateSnapshots: 'missing',
|
updateSnapshots: 'missing',
|
||||||
version: require('../package.json').version,
|
version: require('../package.json').version,
|
||||||
workers,
|
workers: 0,
|
||||||
webServer: null,
|
webServer: null,
|
||||||
_watchMode: false,
|
_watchMode: false,
|
||||||
_webServers: [],
|
_webServers: [],
|
||||||
|
|
|
||||||
16
packages/playwright-test/types/test.d.ts
vendored
16
packages/playwright-test/types/test.d.ts
vendored
|
|
@ -936,13 +936,14 @@ interface TestConfig {
|
||||||
updateSnapshots?: "all"|"none"|"missing";
|
updateSnapshots?: "all"|"none"|"missing";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of concurrent worker processes to use for parallelizing tests.
|
* The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of
|
||||||
|
* logical CPU cores, e.g. `'50%'.`
|
||||||
*
|
*
|
||||||
* Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to
|
* Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to
|
||||||
* speed up test execution.
|
* speed up test execution.
|
||||||
*
|
*
|
||||||
* Defaults to one half of the number of CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with
|
* Defaults to half of the number of logical CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel)
|
||||||
* Playwright Test.
|
* with Playwright Test.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* // playwright.config.ts
|
* // playwright.config.ts
|
||||||
|
|
@ -955,7 +956,7 @@ interface TestConfig {
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
workers?: number;}
|
workers?: number|string;}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or
|
* Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or
|
||||||
|
|
@ -1210,13 +1211,14 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||||
*/
|
*/
|
||||||
updateSnapshots: 'all' | 'none' | 'missing';
|
updateSnapshots: 'all' | 'none' | 'missing';
|
||||||
/**
|
/**
|
||||||
* The maximum number of concurrent worker processes to use for parallelizing tests.
|
* The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of
|
||||||
|
* logical CPU cores, e.g. `'50%'.`
|
||||||
*
|
*
|
||||||
* Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to
|
* Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to
|
||||||
* speed up test execution.
|
* speed up test execution.
|
||||||
*
|
*
|
||||||
* Defaults to one half of the number of CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with
|
* Defaults to half of the number of logical CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel)
|
||||||
* Playwright Test.
|
* with Playwright Test.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* // playwright.config.ts
|
* // playwright.config.ts
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,41 @@ test('should support ignoreSnapshots config option', async ({ runInlineTest }) =
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should validate workers option set to percent', async ({ runInlineTest }, testInfo) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
workers: '50%'
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('pass', async () => {
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw when workers option is invalid', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
workers: ''
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('pass', async () => {
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.output).toContain('config.workers must be a number or percentage');
|
||||||
|
});
|
||||||
|
|
||||||
test('should work with undefined values and base', async ({ runInlineTest }) => {
|
test('should work with undefined values and base', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue