mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-07 11:46:42 +03:00
feat(shard): introduce mode: 'default'
(#23023)
This mode allows a suite to opt-out from parallelism. Useful to setup multiple suites running in parallel, with each suite not being sharded. References #22891.
This commit is contained in:
parent
969e5ff1aa
commit
ab7e794bf7
@ -284,9 +284,27 @@ Learn more about the execution modes [here](../test-parallel.md).
|
|||||||
test('runs second', async ({ page }) => {});
|
test('runs second', async ({ page }) => {});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Run multiple describes in parallel, but tests inside each describe in order.
|
||||||
|
|
||||||
|
```js
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
test.describe('A, runs in parallel with B', () => {
|
||||||
|
test.describe.configure({ mode: 'default' });
|
||||||
|
test('in order A1', async ({ page }) => {});
|
||||||
|
test('in order A2', async ({ page }) => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('B, runs in parallel with A', () => {
|
||||||
|
test.describe.configure({ mode: 'default' });
|
||||||
|
test('in order B1', async ({ page }) => {});
|
||||||
|
test('in order B2', async ({ page }) => {});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### option: Test.describe.configure.mode
|
### option: Test.describe.configure.mode
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- `mode` <[TestMode]<"parallel"|"serial">>
|
- `mode` <[TestMode]<"default"|"parallel"|"serial">>
|
||||||
|
|
||||||
Execution mode. Learn more about the execution modes [here](../test-parallel.md).
|
Execution mode. Learn more about the execution modes [here](../test-parallel.md).
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export class Suite extends Base implements SuitePrivate {
|
|||||||
_retries: number | undefined;
|
_retries: number | undefined;
|
||||||
_staticAnnotations: Annotation[] = [];
|
_staticAnnotations: Annotation[] = [];
|
||||||
_modifiers: Modifier[] = [];
|
_modifiers: Modifier[] = [];
|
||||||
_parallelMode: 'default' | 'serial' | 'parallel' = 'default';
|
_parallelMode: 'none' | 'default' | 'serial' | 'parallel' = 'none';
|
||||||
_fullProject: FullProjectInternal | undefined;
|
_fullProject: FullProjectInternal | undefined;
|
||||||
_fileId: string | undefined;
|
_fileId: string | undefined;
|
||||||
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
||||||
|
@ -124,6 +124,8 @@ export class TestTypeImpl {
|
|||||||
for (let parent: Suite | undefined = suite; parent; parent = parent.parent) {
|
for (let parent: Suite | undefined = suite; parent; parent = parent.parent) {
|
||||||
if (parent._parallelMode === 'serial' && child._parallelMode === 'parallel')
|
if (parent._parallelMode === 'serial' && child._parallelMode === 'parallel')
|
||||||
throw new Error('describe.parallel cannot be nested inside describe.serial');
|
throw new Error('describe.parallel cannot be nested inside describe.serial');
|
||||||
|
if (parent._parallelMode === 'default' && child._parallelMode === 'parallel')
|
||||||
|
throw new Error('describe.parallel cannot be nested inside describe with default mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentlyLoadingFileSuite(child);
|
setCurrentlyLoadingFileSuite(child);
|
||||||
@ -138,7 +140,7 @@ export class TestTypeImpl {
|
|||||||
suite._hooks.push({ type: name, fn, location });
|
suite._hooks.push({ type: name, fn, location });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, `test.describe.configure()`);
|
const suite = this._currentSuite(location, `test.describe.configure()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
@ -151,12 +153,14 @@ export class TestTypeImpl {
|
|||||||
suite._retries = options.retries;
|
suite._retries = options.retries;
|
||||||
|
|
||||||
if (options.mode !== undefined) {
|
if (options.mode !== undefined) {
|
||||||
if (suite._parallelMode !== 'default')
|
if (suite._parallelMode !== 'none')
|
||||||
throw new Error('Parallel mode is already assigned for the enclosing scope.');
|
throw new Error(`"${suite._parallelMode}" mode is already assigned for the enclosing scope.`);
|
||||||
suite._parallelMode = options.mode;
|
suite._parallelMode = options.mode;
|
||||||
for (let parent: Suite | undefined = suite.parent; parent; parent = parent.parent) {
|
for (let parent: Suite | undefined = suite.parent; parent; parent = parent.parent) {
|
||||||
if (parent._parallelMode === 'serial' && suite._parallelMode === 'parallel')
|
if (parent._parallelMode === 'serial' && suite._parallelMode === 'parallel')
|
||||||
throw new Error('describe.parallel cannot be nested inside describe.serial');
|
throw new Error('describe with parallel mode cannot be nested inside describe with serial mode');
|
||||||
|
if (parent._parallelMode === 'default' && suite._parallelMode === 'parallel')
|
||||||
|
throw new Error('describe with parallel mode cannot be nested inside describe with default mode');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export type JsonSuite = {
|
|||||||
suites: JsonSuite[];
|
suites: JsonSuite[];
|
||||||
tests: JsonTestCase[];
|
tests: JsonTestCase[];
|
||||||
fileId: string | undefined;
|
fileId: string | undefined;
|
||||||
parallelMode: 'default' | 'serial' | 'parallel';
|
parallelMode: 'none' | 'default' | 'serial' | 'parallel';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestCase = {
|
export type JsonTestCase = {
|
||||||
@ -383,7 +383,7 @@ export class TeleSuite implements SuitePrivate {
|
|||||||
_timeout: number | undefined;
|
_timeout: number | undefined;
|
||||||
_retries: number | undefined;
|
_retries: number | undefined;
|
||||||
_fileId: string | undefined;
|
_fileId: string | undefined;
|
||||||
_parallelMode: 'default' | 'serial' | 'parallel' = 'default';
|
_parallelMode: 'none' | 'default' | 'serial' | 'parallel' = 'none';
|
||||||
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
||||||
|
|
||||||
constructor(title: string, type: 'root' | 'project' | 'file' | 'describe') {
|
constructor(title: string, type: 'root' | 'project' | 'file' | 'describe') {
|
||||||
|
@ -79,20 +79,20 @@ export function createTestGroups(projectSuite: Suite, workers: number): TestGrou
|
|||||||
|
|
||||||
// Note that a parallel suite cannot be inside a serial suite. This is enforced in TestType.
|
// Note that a parallel suite cannot be inside a serial suite. This is enforced in TestType.
|
||||||
let insideParallel = false;
|
let insideParallel = false;
|
||||||
let outerMostSerialSuite: Suite | undefined;
|
let outerMostSequentialSuite: Suite | undefined;
|
||||||
let hasAllHooks = false;
|
let hasAllHooks = false;
|
||||||
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent) {
|
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent) {
|
||||||
if (parent._parallelMode === 'serial')
|
if (parent._parallelMode === 'serial' || parent._parallelMode === 'default')
|
||||||
outerMostSerialSuite = parent;
|
outerMostSequentialSuite = parent;
|
||||||
insideParallel = insideParallel || parent._parallelMode === 'parallel';
|
insideParallel = insideParallel || parent._parallelMode === 'parallel';
|
||||||
hasAllHooks = hasAllHooks || parent._hooks.some(hook => hook.type === 'beforeAll' || hook.type === 'afterAll');
|
hasAllHooks = hasAllHooks || parent._hooks.some(hook => hook.type === 'beforeAll' || hook.type === 'afterAll');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insideParallel) {
|
if (insideParallel) {
|
||||||
if (hasAllHooks && !outerMostSerialSuite) {
|
if (hasAllHooks && !outerMostSequentialSuite) {
|
||||||
withRequireFile.parallelWithHooks.tests.push(test);
|
withRequireFile.parallelWithHooks.tests.push(test);
|
||||||
} else {
|
} else {
|
||||||
const key = outerMostSerialSuite || test;
|
const key = outerMostSequentialSuite || test;
|
||||||
let group = withRequireFile.parallel.get(key);
|
let group = withRequireFile.parallel.get(key);
|
||||||
if (!group) {
|
if (!group) {
|
||||||
group = createGroup(test);
|
group = createGroup(test);
|
||||||
|
@ -18,5 +18,5 @@ import type { Suite } from './testReporter';
|
|||||||
|
|
||||||
export interface SuitePrivate extends Suite {
|
export interface SuitePrivate extends Suite {
|
||||||
_fileId: string | undefined;
|
_fileId: string | undefined;
|
||||||
_parallelMode: 'default' | 'serial' | 'parallel';
|
_parallelMode: 'none' | 'default' | 'serial' | 'parallel';
|
||||||
}
|
}
|
||||||
|
20
packages/playwright-test/types/test.d.ts
vendored
20
packages/playwright-test/types/test.d.ts
vendored
@ -2626,9 +2626,27 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
* test('runs second', async ({ page }) => {});
|
* test('runs second', async ({ page }) => {});
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* - Run multiple describes in parallel, but tests inside each describe in order.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* test.describe.configure({ mode: 'parallel' });
|
||||||
|
*
|
||||||
|
* test.describe('A, runs in parallel with B', () => {
|
||||||
|
* test.describe.configure({ mode: 'default' });
|
||||||
|
* test('in order A1', async ({ page }) => {});
|
||||||
|
* test('in order A2', async ({ page }) => {});
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* test.describe('B, runs in parallel with A', () => {
|
||||||
|
* test.describe.configure({ mode: 'default' });
|
||||||
|
* test('in order B1', async ({ page }) => {});
|
||||||
|
* test('in order B2', async ({ page }) => {});
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
configure: (options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) => void;
|
configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) => void;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Declares a skipped test, similarly to
|
* Declares a skipped test, similarly to
|
||||||
|
@ -225,3 +225,62 @@ test('should skip dependency when project is sharded out', async ({ runInlineTes
|
|||||||
'test in tests2',
|
'test in tests2',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not shard mode:default suites', async ({ runInlineTest }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22891' });
|
||||||
|
|
||||||
|
const tests = {
|
||||||
|
'a1.spec.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
test('test0', async ({ }) => {
|
||||||
|
console.log('\\n%%test0');
|
||||||
|
});
|
||||||
|
test('test1', async ({ }) => {
|
||||||
|
console.log('\\n%%test1');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'a2.spec.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
test.describe(() => {
|
||||||
|
test.describe.configure({ mode: 'default' });
|
||||||
|
test.beforeAll(() => {
|
||||||
|
console.log('\\n%%beforeAll1');
|
||||||
|
});
|
||||||
|
test('test2', async ({ }) => {
|
||||||
|
console.log('\\n%%test2');
|
||||||
|
});
|
||||||
|
test('test3', async ({ }) => {
|
||||||
|
console.log('\\n%%test3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe(() => {
|
||||||
|
test.describe.configure({ mode: 'default' });
|
||||||
|
test.beforeAll(() => {
|
||||||
|
console.log('\\n%%beforeAll2');
|
||||||
|
});
|
||||||
|
test('test4', async ({ }) => {
|
||||||
|
console.log('\\n%%test4');
|
||||||
|
});
|
||||||
|
test('test5', async ({ }) => {
|
||||||
|
console.log('\\n%%test5');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
const result = await runInlineTest(tests, { shard: '2/3', workers: 1 });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(2);
|
||||||
|
expect(result.outputLines).toEqual(['beforeAll1', 'test2', 'test3']);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const result = await runInlineTest(tests, { shard: '3/3', workers: 1 });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(2);
|
||||||
|
expect(result.outputLines).toEqual(['beforeAll2', 'test4', 'test5']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
2
utils/generate_types/overrides-test.d.ts
vendored
2
utils/generate_types/overrides-test.d.ts
vendored
@ -131,7 +131,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
parallel: SuiteFunction & {
|
parallel: SuiteFunction & {
|
||||||
only: SuiteFunction;
|
only: SuiteFunction;
|
||||||
};
|
};
|
||||||
configure: (options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) => void;
|
configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) => void;
|
||||||
};
|
};
|
||||||
skip(title: string, testFunction: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<void> | void): void;
|
skip(title: string, testFunction: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<void> | void): void;
|
||||||
skip(): void;
|
skip(): void;
|
||||||
|
Loading…
Reference in New Issue
Block a user