Revert "chore: hide setup, store, TestProject.setupMatch, storageStat… (#19756)

…eName (#19442)"

This reverts commit 92dd734e04.
This commit is contained in:
Yury Semikhatsky 2022-12-28 15:39:31 -08:00 коммит произвёл GitHub
Родитель d912cbf115
Коммит 137070d889
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 537 добавлений и 337 удалений

Просмотреть файл

@ -205,6 +205,14 @@ Learn more about [automatic screenshots](../test-configuration.md#automatic-scre
## property: TestOptions.storageState = %%-js-python-context-option-storage-state-%%
* since: v1.10
## property: TestOptions.storageStateName
* since: v1.29
- type: <[string]>
Name of the [TestStore] entry that should be used to initialize [`property: TestOptions.storageState`]. The value must be
written to the test storage before creation of a browser context that uses it (usually in [`property: TestProject.setupMatch`]). If both
this property and [`property: TestOptions.storageState`] are specified, this property will always take precedence.
## property: TestOptions.testIdAttribute
* since: v1.27

Просмотреть файл

@ -146,7 +146,7 @@ Filter to only run tests with a title matching one of the patterns. For example,
* since: v1.10
- type: ?<[RegExp]|[Array]<[RegExp]>>
Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of [`property: TestProject.grep`]. Also available globally and in the [command line](../test-cli.md) with the `--grep-invert` option.
Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of [`property: TestProject.grep`]. Also available globally and in the [command line](../test-cli.md) with the `--grep-invert` option. This filter and its command line counterpart also applies to the setup files. If all [`property: TestProject.setupMatch`] tests match the filter Playwright **will** run all setup files before running the matching tests.
`grepInvert` option is also useful for [tagging tests](../test-annotations.md#tag-tests).
@ -162,6 +162,18 @@ Metadata that will be put directly to the test report serialized as JSON.
Project name is visible in the report and during test execution.
## property: TestProject.setupMatch
* since: v1.29
- type: ?<[string]|[RegExp]|[Array]<[string]|[RegExp]>>
Project setup files that will be executed before all tests in the project.
**Details**
If project setup fails the tests in this project will be skipped. All project setup files will run in every shard if the project is sharded. [`property: TestProject.grep`] and [`property: TestProject.grepInvert`] and their command line counterparts also apply to the setup files. If such filters match only tests in the project, Playwright will run **all** setup files before running the matching tests.
If there is a file that matches both [`property: TestProject.setupMatch`] and [`property: TestProject.testMatch`] filters an error will be thrown.
## property: TestProject.snapshotDir
* since: v1.10
- type: ?<[string]>

Просмотреть файл

@ -0,0 +1,56 @@
# class: TestStore
* since: v1.29
* langs: js
Playwright Test provides a global `store` object for passing values between project setup and tests. It is
an error to call store methods outside of setup and tests.
```js tab=js-js
const { setup, store } = require('@playwright/test');
setup('sign in', async ({ page, context }) => {
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await context.storageState();
await store.set('test-user', contextState)
});
```
```js tab=js-ts
import { setup, store } from '@playwright/test';
setup('sign in', async ({ page, context }) => {
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await context.storageState();
await store.set('test-user', contextState)
});
```
## async method: TestStore.get
* since: v1.29
- returns: <[any]>
Get named item from the store. Returns undefined if there is no value with given name.
### param: TestStore.get.name
* since: v1.29
- `name` <[string]>
Item name.
## async method: TestStore.set
* since: v1.29
Set value to the store.
### param: TestStore.set.name
* since: v1.29
- `name` <[string]>
Item name.
### param: TestStore.set.value
* since: v1.29
- `value` <[any]>
Item value. The value must be serializable to JSON. Passing `undefined` deletes the entry with given name.

Просмотреть файл

@ -27,7 +27,7 @@ import { rootTestType, _setProjectSetup } from './testType';
export { expect } from './expect';
export { addRunnerPlugin as _addRunnerPlugin } from './plugins';
export const _baseTest: TestType<{}, {}> = rootTestType.test;
export const _store = _baseStore;
export const store = _baseStore;
if ((process as any)['__pw_initiator__']) {
const originalStackTraceLimit = Error.stackTraceLimit;
@ -47,7 +47,6 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
_reuseContext: boolean,
_setupContextOptionsAndArtifacts: void;
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
_storageStateName: string | undefined;
};
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
_browserOptions: LaunchOptions;
@ -144,7 +143,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
_storageStateName: [undefined, { option: true }],
storageStateName: [undefined, { option: true }],
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === undefined ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
@ -174,7 +173,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
permissions,
proxy,
storageState,
_storageStateName,
storageStateName,
viewport,
timezoneId,
userAgent,
@ -213,10 +212,10 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
options.permissions = permissions;
if (proxy !== undefined)
options.proxy = proxy;
if (_storageStateName !== undefined) {
const value = await _store.get(_storageStateName);
if (storageStateName !== undefined) {
const value = await store.get(storageStateName);
if (!value)
throw new Error(`Cannot find value in the _store for _storageStateName: "${_storageStateName}"`);
throw new Error(`Cannot find value in the store for storageStateName: "${storageStateName}"`);
options.storageState = value as any;
} else if (storageState !== undefined) {
options.storageState = storageState;
@ -629,7 +628,7 @@ function normalizeScreenshotMode(screenshot: PlaywrightWorkerOptions['screenshot
const kTracingStarted = Symbol('kTracingStarted');
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
export const _setup = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
_setProjectSetup(_setup, true);
export const setup = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
_setProjectSetup(setup, true);
export default test;

Просмотреть файл

@ -275,7 +275,7 @@ export class Loader {
const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results'));
const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir);
const name = takeFirst(projectConfig.name, config.name, '');
const _setupMatch = takeFirst((projectConfig as any)._setupMatch, []);
const _setupMatch = takeFirst(projectConfig.setupMatch, []);
const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
const snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
@ -614,7 +614,7 @@ function validateProject(file: string, project: Project, title: string) {
throw errorWithFile(file, `${title}.testDir must be a string`);
}
for (const prop of ['testIgnore', 'testMatch'] as const) {
for (const prop of ['testIgnore', 'testMatch', 'setupMatch'] as const) {
if (prop in project && project[prop] !== undefined) {
const value = project[prop];
if (Array.isArray(value)) {

Просмотреть файл

@ -258,13 +258,13 @@ export class Runner {
if (!isTest && !isSetup)
return false;
if (isSetup && isTest)
throw new Error(`File "${file}" matches both '_setup' and 'testMatch' filters in project "${project.name}"`);
throw new Error(`File "${file}" matches both 'setup' and 'testMatch' filters in project "${project.name}"`);
if (fileToProjectName.has(file)) {
if (isSetup) {
if (!setupFiles.has(file))
throw new Error(`File "${file}" matches '_setup' filter in project "${project.name}" and 'testMatch' filter in project "${fileToProjectName.get(file)}"`);
throw new Error(`File "${file}" matches 'setup' filter in project "${project.name}" and 'testMatch' filter in project "${fileToProjectName.get(file)}"`);
} else if (setupFiles.has(file)) {
throw new Error(`File "${file}" matches '_setup' filter in project "${fileToProjectName.get(file)}" and 'testMatch' filter in project "${project.name}"`);
throw new Error(`File "${file}" matches 'setup' filter in project "${fileToProjectName.get(file)}" and 'testMatch' filter in project "${project.name}"`);
}
}
fileToProjectName.set(file, project.name);

Просмотреть файл

@ -16,10 +16,11 @@
import fs from 'fs';
import path from 'path';
import type { TestStore } from '../types/test';
import { currentTestInfo } from './globals';
import { sanitizeForFilePath, trimLongString } from './util';
class JsonStore {
class JsonStore implements TestStore {
private _toFilePath(name: string) {
const testInfo = currentTestInfo();
if (!testInfo)

Просмотреть файл

@ -84,14 +84,14 @@ export class TestTypeImpl {
if (this._projectSetup)
addFatalError(`${title} is called in a file which is not a part of project setup.`, location);
else
addFatalError(`${title} is called in a project setup file (use '_setup' instead of 'test').`, location);
addFatalError(`${title} is called in a project setup file (use 'setup' instead of 'test').`, location);
}
return suite;
}
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme', location: Location, title: string, fn: Function) {
throwIfRunningInsideJest();
const suite = this._currentSuite(location, this._projectSetup ? '_setup()' : 'test()');
const suite = this._currentSuite(location, this._projectSetup ? 'setup()' : 'test()');
if (!suite)
return;
const test = new TestCase(title, fn, this, location);
@ -150,7 +150,7 @@ export class TestTypeImpl {
}
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, fn: Function) {
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.${name}()`);
const suite = this._currentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.${name}()`);
if (!suite)
return;
suite._hooks.push({ type: name, fn, location });
@ -158,7 +158,7 @@ export class TestTypeImpl {
private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) {
throwIfRunningInsideJest();
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.describe.configure()`);
const suite = this._currentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.describe.configure()`);
if (!suite)
return;
@ -225,7 +225,7 @@ export class TestTypeImpl {
}
private _use(location: Location, fixtures: Fixtures) {
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.use()`);
const suite = this._currentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.use()`);
if (!suite)
return;
suite._use.push({ fixtures, location });
@ -234,7 +234,7 @@ export class TestTypeImpl {
private async _step<T>(location: Location, title: string, body: () => Promise<T>): Promise<T> {
const testInfo = currentTestInfo();
if (!testInfo) {
addFatalError(`${this._projectSetup ? '_setup' : 'test'}.step() can only be called from a test`, location);
addFatalError(`${this._projectSetup ? 'setup' : 'test'}.step() can only be called from a test`, location);
return undefined as any;
}
const step = testInfo._addStep({

68
packages/playwright-test/types/test.d.ts поставляемый
Просмотреть файл

@ -193,7 +193,10 @@ export interface FullProject<TestArgs = {}, WorkerArgs = {}> {
/**
* Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of
* [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep). Also available globally
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option.
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option. This filter and its command line
* counterpart also applies to the setup files. If all
* [testProject.setupMatch](https://playwright.dev/docs/api/class-testproject#test-project-setup-match) tests match
* the filter Playwright **will** run all setup files before running the matching tests.
*
* `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests).
*/
@ -2826,6 +2829,35 @@ type ConnectOptions = {
timeout?: number;
};
/**
* Playwright Test provides a global `store` object for passing values between project setup and tests. It is an error
* to call store methods outside of setup and tests.
*
* ```js
* import { setup, store } from '@playwright/test';
*
* setup('sign in', async ({ page, context }) => {
* // Save signed-in state to an entry named 'github-test-user'.
* const contextState = await context.storageState();
* await store.set('test-user', contextState)
* });
* ```
*
*/
export interface TestStore {
/**
* Get named item from the store. Returns undefined if there is no value with given name.
* @param name Item name.
*/
get<T>(name: string): Promise<T | undefined>;
/**
* Set value to the store.
* @param name Item name.
* @param value Item value. The value must be serializable to JSON. Passing `undefined` deletes the entry with given name.
*/
set<T>(name: string, value: T | undefined): Promise<void>;
}
/**
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
*
@ -3062,6 +3094,16 @@ export interface PlaywrightTestOptions {
* Either a path to the file with saved storage, or an object with the following fields:
*/
storageState: StorageState | undefined;
/**
* Name of the [TestStore] entry that should be used to initialize
* [testOptions.storageState](https://playwright.dev/docs/api/class-testoptions#test-options-storage-state). The value
* must be written to the test storage before creation of a browser context that uses it (usually in
* [testProject.setupMatch](https://playwright.dev/docs/api/class-testproject#test-project-setup-match)). If both this
* property and
* [testOptions.storageState](https://playwright.dev/docs/api/class-testoptions#test-options-storage-state) are
* specified, this property will always take precedence.
*/
storageStateName: string | undefined;
/**
* Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
@ -3345,6 +3387,7 @@ export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect;
export const store: TestStore;
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};
@ -4639,7 +4682,10 @@ interface TestProject {
/**
* Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of
* [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep). Also available globally
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option.
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option. This filter and its command line
* counterpart also applies to the setup files. If all
* [testProject.setupMatch](https://playwright.dev/docs/api/class-testproject#test-project-setup-match) tests match
* the filter Playwright **will** run all setup files before running the matching tests.
*
* `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests).
*/
@ -4655,6 +4701,24 @@ interface TestProject {
*/
name?: string;
/**
* Project setup files that will be executed before all tests in the project.
*
* **Details**
*
* If project setup fails the tests in this project will be skipped. All project setup files will run in every shard
* if the project is sharded. [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep)
* and [testProject.grepInvert](https://playwright.dev/docs/api/class-testproject#test-project-grep-invert) and their
* command line counterparts also apply to the setup files. If such filters match only tests in the project,
* Playwright will run **all** setup files before running the matching tests.
*
* If there is a file that matches both
* [testProject.setupMatch](https://playwright.dev/docs/api/class-testproject#test-project-setup-match) and
* [testProject.testMatch](https://playwright.dev/docs/api/class-testproject#test-project-test-match) filters an error
* will be thrown.
*/
setupMatch?: string|RegExp|Array<string|RegExp>;
/**
* The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to
* [testProject.testDir](https://playwright.dev/docs/api/class-testproject#test-project-test-dir).

Просмотреть файл

@ -480,3 +480,41 @@ test('should have correct types for the config', async ({ runTSC }) => {
});
expect(result.exitCode).toBe(0);
});
test('should throw when project.setupMatch has wrong type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a', setupMatch: 100 },
],
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`Error: playwright.config.ts: config.projects[0].setupMatch must be a string or a RegExp`);
});
test('should throw when project.setupMatch has wrong array type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a', setupMatch: [/100/, 100] },
],
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`Error: playwright.config.ts: config.projects[0].setupMatch[1] must be a string or a RegExp`);
});

Просмотреть файл

@ -17,7 +17,7 @@ import type { PlaywrightTestConfig, TestInfo, PlaywrightTestProject } from '@pla
import path from 'path';
import { test, expect } from './playwright-test-fixtures';
function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTemplates?: { [name: string]: PlaywrightTestProject & any }): Record<string, string> {
function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTemplates?: { [name: string]: PlaywrightTestProject }): Record<string, string> {
const config: PlaywrightTestConfig = {
projects: names.map(name => ({ ...projectTemplates?.[name], name, testDir: testInfo.outputPath(name) })),
};
@ -28,9 +28,9 @@ function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTe
test('${name} test', async () => {
await new Promise(f => setTimeout(f, 100));
});`;
files[`${name}/${name}._setup.ts`] = `
const { _setup } = pwt;
_setup('${name} _setup', async () => {
files[`${name}/${name}.setup.ts`] = `
const { setup } = pwt;
setup('${name} setup', async () => {
await new Promise(f => setTimeout(f, 100));
});`;
}
@ -98,15 +98,15 @@ function expectFilesRunBefore(timeline: Timeline, before: string[], after: strin
test('should work for one project', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
_setupMatch: ['**/*._setup.ts']
setupMatch: ['**/*.setup.ts']
},
};
const configWithFiles = createConfigWithProjects(['a'], testInfo, projectTemplates);
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(formatTimeline(timeline)).toEqual(`a > a${path.sep}a._setup.ts > a _setup [begin]
a > a${path.sep}a._setup.ts > a _setup [end]
expect(formatTimeline(timeline)).toEqual(`a > a${path.sep}a.setup.ts > a setup [begin]
a > a${path.sep}a.setup.ts > a setup [end]
a > a${path.sep}a.spec.ts > a test [begin]
a > a${path.sep}a.spec.ts > a test [end]`);
});
@ -114,13 +114,13 @@ a > a${path.sep}a.spec.ts > a test [end]`);
test('should work for several projects', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
_setupMatch: ['**/*._setup.ts']
setupMatch: ['**/*.setup.ts']
},
'b': {
_setupMatch: /.*b._setup.ts/
setupMatch: /.*b.setup.ts/
},
'c': {
_setupMatch: '**/c._setup.ts'
setupMatch: '**/c.setup.ts'
},
};
const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates);
@ -128,22 +128,22 @@ test('should work for several projects', async ({ runGroups }, testInfo) => {
expect(exitCode).toBe(0);
expect(passed).toBe(6);
for (const name of ['a', 'b', 'c'])
expectFilesRunBefore(timeline, [`${name}${path.sep}${name}._setup.ts`], [`${name}${path.sep}${name}.spec.ts`]);
expectFilesRunBefore(timeline, [`${name}${path.sep}${name}.setup.ts`], [`${name}${path.sep}${name}.spec.ts`]);
});
test('should stop project if _setup fails', async ({ runGroups }, testInfo) => {
test('should stop project if setup fails', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
_setupMatch: ['**/*._setup.ts']
setupMatch: ['**/*.setup.ts']
},
'b': {
_setupMatch: /.*b._setup.ts/
setupMatch: /.*b.setup.ts/
},
};
const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates);
configWithFiles[`a/a._setup.ts`] = `
const { _setup, expect } = pwt;
_setup('a _setup', async () => {
configWithFiles[`a/a.setup.ts`] = `
const { setup, expect } = pwt;
setup('a setup', async () => {
expect(1).toBe(2);
});`;
@ -152,17 +152,17 @@ test('should stop project if _setup fails', async ({ runGroups }, testInfo) => {
expect(passed).toBe(3);
expect(skipped).toBe(1); // 1 test from project 'a'
for (const name of ['a', 'b'])
expectFilesRunBefore(timeline, [`${name}${path.sep}${name}._setup.ts`], [`${name}${path.sep}${name}.spec.ts`]);
expectFilesRunBefore(timeline, [`${name}${path.sep}${name}.setup.ts`], [`${name}${path.sep}${name}.spec.ts`]);
});
test('should run _setup in each project shard', async ({ runGroups }, testInfo) => {
test('should run setup in each project shard', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -178,44 +178,44 @@ test('should run _setup in each project shard', async ({ runGroups }, testInfo)
test('test1', async () => { });
test('test2', async () => { });
`,
'c._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'c.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
};
{ // Shard 1/2
const { exitCode, passed, timeline, output } = await runGroups(files, { shard: '1/2' });
expect(output).toContain('Running 6 tests using 1 worker, shard 1 of 2');
expect(fileNames(timeline)).toEqual(['a.test.ts', 'c._setup.ts']);
expectFilesRunBefore(timeline, [`c._setup.ts`], [`a.test.ts`]);
expect(fileNames(timeline)).toEqual(['a.test.ts', 'c.setup.ts']);
expectFilesRunBefore(timeline, [`c.setup.ts`], [`a.test.ts`]);
expect(exitCode).toBe(0);
expect(passed).toBe(6);
}
{ // Shard 2/2
const { exitCode, passed, timeline, output } = await runGroups(files, { shard: '2/2' });
expect(output).toContain('Running 4 tests using 1 worker, shard 2 of 2');
expect(fileNames(timeline)).toEqual(['b.test.ts', 'c._setup.ts']);
expectFilesRunBefore(timeline, [`c._setup.ts`], [`b.test.ts`]);
expect(fileNames(timeline)).toEqual(['b.test.ts', 'c.setup.ts']);
expectFilesRunBefore(timeline, [`c.setup.ts`], [`b.test.ts`]);
expect(exitCode).toBe(0);
expect(passed).toBe(4);
}
});
test('should run _setup only for projects that have tests in the shard', async ({ runGroups }, testInfo) => {
test('should run setup only for projects that have tests in the shard', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*p1._setup.ts$/,
setupMatch: /.*p1.setup.ts$/,
testMatch: /.*a.test.ts/,
},
{
name: 'p2',
_setupMatch: /.*p2._setup.ts$/,
setupMatch: /.*p2.setup.ts$/,
testMatch: /.*b.test.ts/,
},
]
@ -232,60 +232,60 @@ test('should run _setup only for projects that have tests in the shard', async (
test('test1', async () => { });
test('test2', async () => { });
`,
'p1._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'p1.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'p2._setup.ts': `
const { _setup } = pwt;
_setup('_setup3', async () => { });
_setup('_setup4', async () => { });
'p2.setup.ts': `
const { setup } = pwt;
setup('setup3', async () => { });
setup('setup4', async () => { });
`,
};
{ // Shard 1/2
const { exitCode, passed, timeline, output } = await runGroups(files, { shard: '1/2' });
expect(output).toContain('Running 6 tests using 1 worker, shard 1 of 2');
expect(fileNames(timeline)).toEqual(['a.test.ts', 'p1._setup.ts']);
expectFilesRunBefore(timeline, [`p1._setup.ts`], [`a.test.ts`]);
expect(fileNames(timeline)).toEqual(['a.test.ts', 'p1.setup.ts']);
expectFilesRunBefore(timeline, [`p1.setup.ts`], [`a.test.ts`]);
expect(exitCode).toBe(0);
expect(passed).toBe(6);
}
{ // Shard 2/2
const { exitCode, passed, timeline, output } = await runGroups(files, { shard: '2/2' });
expect(output).toContain('Running 4 tests using 1 worker, shard 2 of 2');
expect(fileNames(timeline)).toEqual(['b.test.ts', 'p2._setup.ts']);
expectFilesRunBefore(timeline, [`p2._setup.ts`], [`b.test.ts`]);
expect(fileNames(timeline)).toEqual(['b.test.ts', 'p2.setup.ts']);
expectFilesRunBefore(timeline, [`p2.setup.ts`], [`b.test.ts`]);
expect(exitCode).toBe(0);
expect(passed).toBe(4);
}
});
test('--project only runs _setup from that project;', async ({ runGroups }, testInfo) => {
test('--project only runs setup from that project;', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
_setupMatch: /.*a._setup.ts/
setupMatch: /.*a.setup.ts/
},
'b': {
_setupMatch: /.*b._setup.ts/
setupMatch: /.*b.setup.ts/
},
};
const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates);
const { exitCode, passed, timeline } = await runGroups(configWithFiles, { project: ['a', 'c'] });
expect(exitCode).toBe(0);
expect(passed).toBe(3);
expect(fileNames(timeline)).toEqual(['a._setup.ts', 'a.spec.ts', 'c.spec.ts']);
expect(fileNames(timeline)).toEqual(['a.setup.ts', 'a.spec.ts', 'c.spec.ts']);
});
test('same file cannot be a _setup and a test in the same project', async ({ runGroups }, testInfo) => {
test('same file cannot be a setup and a test in the same project', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*a.test.ts$/,
setupMatch: /.*a.test.ts$/,
testMatch: /.*a.test.ts$/,
},
]
@ -298,22 +298,22 @@ test('same file cannot be a _setup and a test in the same project', async ({ run
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(1);
expect(output).toContain(`a.test.ts" matches both '_setup' and 'testMatch' filters in project "p1"`);
expect(output).toContain(`a.test.ts" matches both 'setup' and 'testMatch' filters in project "p1"`);
});
test('same file cannot be a _setup and a test in different projects', async ({ runGroups }, testInfo) => {
test('same file cannot be a setup and a test in different projects', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*a.test.ts$/,
setupMatch: /.*a.test.ts$/,
testMatch: /.*noMatch.test.ts$/,
},
{
name: 'p2',
_setupMatch: /.*noMatch.test.ts$/,
setupMatch: /.*noMatch.test.ts$/,
testMatch: /.*a.test.ts$/
},
]
@ -326,41 +326,41 @@ test('same file cannot be a _setup and a test in different projects', async ({ r
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(1);
expect(output).toContain(`a.test.ts" matches '_setup' filter in project "p1" and 'testMatch' filter in project "p2"`);
expect(output).toContain(`a.test.ts" matches 'setup' filter in project "p1" and 'testMatch' filter in project "p2"`);
});
test('list-files should enumerate _setup files in same group', async ({ runCommand }, testInfo) => {
test('list-files should enumerate setup files in same group', async ({ runCommand }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*a.._setup.ts$/,
setupMatch: /.*a..setup.ts$/,
testMatch: /.*a.test.ts$/,
},
{
name: 'p2',
_setupMatch: /.*b._setup.ts$/,
setupMatch: /.*b.setup.ts$/,
testMatch: /.*b.test.ts$/
},
]
};`,
'a1._setup.ts': `
const { _setup } = pwt;
_setup('test1', async () => { });
'a1.setup.ts': `
const { setup } = pwt;
setup('test1', async () => { });
`,
'a2._setup.ts': `
const { _setup } = pwt;
_setup('test1', async () => { });
'a2.setup.ts': `
const { setup } = pwt;
setup('test1', async () => { });
`,
'a.test.ts': `
const { test } = pwt;
test('test2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('test3', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('test3', async () => { });
`,
'b.test.ts': `
const { test } = pwt;
@ -372,42 +372,42 @@ test('list-files should enumerate _setup files in same group', async ({ runComma
expect(exitCode).toBe(0);
const json = JSON.parse(output);
expect(json.projects.map(p => p.name)).toEqual(['p1', 'p2']);
expect(json.projects[0].files.map(f => path.basename(f))).toEqual(['a.test.ts', 'a1._setup.ts', 'a2._setup.ts']);
expect(json.projects[1].files.map(f => path.basename(f))).toEqual(['b._setup.ts', 'b.test.ts']);
expect(json.projects[0].files.map(f => path.basename(f))).toEqual(['a.test.ts', 'a1.setup.ts', 'a2.setup.ts']);
expect(json.projects[1].files.map(f => path.basename(f))).toEqual(['b.setup.ts', 'b.test.ts']);
});
test('test --list should enumerate _setup tests as regular ones', async ({ runCommand }, testInfo) => {
test('test --list should enumerate setup tests as regular ones', async ({ runCommand }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*a.._setup.ts$/,
setupMatch: /.*a..setup.ts$/,
testMatch: /.*a.test.ts$/,
},
{
name: 'p2',
_setupMatch: /.*b._setup.ts$/,
setupMatch: /.*b.setup.ts$/,
testMatch: /.*b.test.ts$/
},
]
};`,
'a1._setup.ts': `
const { _setup } = pwt;
_setup('test1', async () => { });
'a1.setup.ts': `
const { setup } = pwt;
setup('test1', async () => { });
`,
'a2._setup.ts': `
const { _setup } = pwt;
_setup('test1', async () => { });
'a2.setup.ts': `
const { setup } = pwt;
setup('test1', async () => { });
`,
'a.test.ts': `
const { test } = pwt;
test('test2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('test3', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('test3', async () => { });
`,
'b.test.ts': `
const { test } = pwt;
@ -419,21 +419,21 @@ test('test --list should enumerate _setup tests as regular ones', async ({ runCo
expect(exitCode).toBe(0);
expect(output).toContain(`Listing tests:
[p1] a.test.ts:6:7 test2
[p1] a1._setup.ts:5:7 test1
[p1] a2._setup.ts:5:7 test1
[p2] b._setup.ts:5:7 test3
[p1] a1.setup.ts:5:7 test1
[p1] a2.setup.ts:5:7 test1
[p2] b.setup.ts:5:7 test3
[p2] b.test.ts:6:7 test4
Total: 5 tests in 5 files`);
});
test('should allow .only in _setup files', async ({ runGroups }, testInfo) => {
test('should allow .only in setup files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -444,31 +444,31 @@ test('should allow .only in _setup files', async ({ runGroups }, testInfo) => {
test('test3', async () => { });
test('test4', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup.only('_setup1', async () => { });
_setup('_setup2', async () => { });
_setup.only('_setup3', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup.only('setup1', async () => { });
setup('setup2', async () => { });
setup.only('setup3', async () => { });
`,
};
const { exitCode, passed, timeline, output } = await runGroups(files);
expect(output).toContain('Running 2 tests using 1 worker');
expect(output).toContain('[p1] a._setup.ts:5:14 _setup1');
expect(output).toContain('[p1] a._setup.ts:7:14 _setup3');
expect(fileNames(timeline)).toEqual(['a._setup.ts']);
expect(output).toContain('[p1] a.setup.ts:5:13 setup1');
expect(output).toContain('[p1] a.setup.ts:7:13 setup3');
expect(fileNames(timeline)).toEqual(['a.setup.ts']);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
});
test('should allow describe.only in _setup files', async ({ runGroups }, testInfo) => {
test('should allow describe.only in setup files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -479,33 +479,33 @@ test('should allow describe.only in _setup files', async ({ runGroups }, testInf
test('test3', async () => { });
test('test4', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup.describe.only('main', () => {
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup.describe.only('main', () => {
setup('setup1', async () => { });
setup('setup2', async () => { });
});
_setup('_setup3', async () => { });
setup('setup3', async () => { });
`,
};
const { exitCode, passed, timeline, output } = await runGroups(files);
expect(output).toContain('Running 2 tests using 1 worker');
expect(output).toContain('[p1] a._setup.ts:6:9 main _setup1');
expect(output).toContain('[p1] a._setup.ts:7:9 main _setup2');
expect(fileNames(timeline)).toEqual(['a._setup.ts']);
expect(output).toContain('[p1] a.setup.ts:6:9 main setup1');
expect(output).toContain('[p1] a.setup.ts:7:9 main setup2');
expect(fileNames(timeline)).toEqual(['a.setup.ts']);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
});
test('should filter describe line in _setup files', async ({ runGroups }, testInfo) => {
test('should filter describe line in setup files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -516,33 +516,33 @@ test('should filter describe line in _setup files', async ({ runGroups }, testIn
test('test3', async () => { });
test('test4', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup.describe('main', () => {
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup.describe('main', () => {
setup('setup1', async () => { });
setup('setup2', async () => { });
});
_setup('_setup3', async () => { });
setup('setup3', async () => { });
`,
};
const { exitCode, passed, timeline, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['a._setup.ts:5'] });
const { exitCode, passed, timeline, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['a.setup.ts:5'] });
expect(output).toContain('Running 2 tests using 1 worker');
expect(output).toContain('[p1] a._setup.ts:6:9 main _setup1');
expect(output).toContain('[p1] a._setup.ts:7:9 main _setup2');
expect(fileNames(timeline)).toEqual(['a._setup.ts']);
expect(output).toContain('[p1] a.setup.ts:6:9 main setup1');
expect(output).toContain('[p1] a.setup.ts:7:9 main setup2');
expect(fileNames(timeline)).toEqual(['a.setup.ts']);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
});
test('should allow .only in both _setup and test files', async ({ runGroups }, testInfo) => {
test('should allow .only in both setup and test files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -553,28 +553,28 @@ test('should allow .only in both _setup and test files', async ({ runGroups }, t
test('test3', async () => { });
test('test4', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup.only('_setup1', async () => { });
_setup('_setup2', async () => { });
_setup('_setup3', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup.only('setup1', async () => { });
setup('setup2', async () => { });
setup('setup3', async () => { });
`,
};
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(0);
expect(output).toContain('[p1] a._setup.ts:5:14 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:13 setup1');
expect(output).toContain('[p1] a.test.ts:7:12 test2');
});
test('should run full _setup when there is test.only', async ({ runGroups }, testInfo) => {
test('should run full setup when there is test.only', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -585,14 +585,14 @@ test('should run full _setup when there is test.only', async ({ runGroups }, tes
test('test3', async () => { });
test('test4', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup3', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup3', async () => { });
`,
};
@ -600,24 +600,24 @@ test('should run full _setup when there is test.only', async ({ runGroups }, tes
expect(exitCode).toBe(0);
expect(passed).toBe(4);
expect(output).toContain('Running 4 tests using 2 workers');
expect(output).toContain('[p1] b._setup.ts:5:7 _setup3');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p1] b.setup.ts:5:7 setup3');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p1] a.test.ts:6:12 test1');
});
test('should allow filtering _setup by file:line', async ({ runGroups }, testInfo) => {
test('should allow filtering setup by file:line', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*a._setup.ts/,
setupMatch: /.*a.setup.ts/,
},
{
name: 'p2',
_setupMatch: /.*b._setup.ts/,
setupMatch: /.*b.setup.ts/,
},
]
};`,
@ -627,14 +627,14 @@ test('should allow filtering _setup by file:line', async ({ runGroups }, testInf
test('test2', async () => { });
test('test3', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
'b.test.ts': `
const { test } = pwt;
@ -645,31 +645,31 @@ test('should allow filtering _setup by file:line', async ({ runGroups }, testInf
};
{
const { exitCode, passed, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*_setup.ts$'] });
const { exitCode, passed, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*setup.ts$'] });
expect(output).toContain('Running 3 tests using 2 workers');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p2] b._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p2] b.setup.ts:5:7 setup1');
expect(exitCode).toBe(0);
expect(passed).toBe(3);
}
{
const { exitCode, passed, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*a._setup.ts:5'] });
const { exitCode, passed, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*a.setup.ts:5'] });
expect(output).toContain('Running 1 test using 1 worker');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(exitCode).toBe(0);
expect(passed).toBe(1);
}
});
test('should support filters matching both _setup and test', async ({ runGroups }, testInfo) => {
test('should support filters matching both setup and test', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -679,14 +679,14 @@ test('should support filters matching both _setup and test', async ({ runGroups
test('test2', async () => { });
test('test3', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
'b.test.ts': `
const { test } = pwt;
@ -694,17 +694,17 @@ test('should support filters matching both _setup and test', async ({ runGroups
`,
};
const { exitCode, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*a.(_setup|test).ts$'] });
const { exitCode, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*a.(setup|test).ts$'] });
expect(exitCode).toBe(0);
expect(output).toContain('Running 5 tests using 1 worker');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p1] a.test.ts:6:7 test1');
expect(output).toContain('[p1] a.test.ts:7:7 test2');
expect(output).toContain('[p1] a.test.ts:8:7 test3');
});
test('should run _setup for a project if tests match only in another project', async ({ runGroups }, testInfo) => {
test('should run setup for a project if tests match only in another project', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
@ -712,12 +712,12 @@ test('should run _setup for a project if tests match only in another project', a
{
name: 'p1',
testMatch: /.*a.test.ts/,
_setupMatch: /.*a._setup.ts/,
setupMatch: /.*a.setup.ts/,
},
{
name: 'p2',
testMatch: /.*b.test.ts/,
_setupMatch: /.*b._setup.ts/,
setupMatch: /.*b.setup.ts/,
},
]
};`,
@ -725,13 +725,13 @@ test('should run _setup for a project if tests match only in another project', a
const { test } = pwt;
test('test1', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
'b.test.ts': `
const { test } = pwt;
@ -742,19 +742,19 @@ test('should run _setup for a project if tests match only in another project', a
const { exitCode, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['.*a.test.ts$'] });
expect(exitCode).toBe(0);
expect(output).toContain('Running 3 tests using 2 workers');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.test.ts:6:7 test1');
expect(output).toContain('[p2] b._setup.ts:5:7 _setup1');
expect(output).toContain('[p2] b.setup.ts:5:7 setup1');
});
test('should run all _setup files if only tests match filter', async ({ runGroups }, testInfo) => {
test('should run all setup files if only tests match filter', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -764,34 +764,34 @@ test('should run all _setup files if only tests match filter', async ({ runGroup
test('test2', async () => { });
test('test3', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
};
const { exitCode, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['a.test.ts:7'] });
expect(exitCode).toBe(0);
expect(output).toContain('Running 4 tests using 2 workers');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p1] b._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p1] b.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.test.ts:7:7 test2');
});
test('should run all _setup files if only tests match grep filter', async ({ runGroups }, testInfo) => {
test('should run all setup files if only tests match grep filter', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
@ -801,35 +801,35 @@ test('should run all _setup files if only tests match grep filter', async ({ run
test('test2', async () => { });
test('test3', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
`,
};
const { exitCode, output } = await runGroups(files, undefined, undefined, { additionalArgs: ['--grep', '.*test2$'] });
expect(exitCode).toBe(0);
expect(output).toContain('Running 4 tests using 2 workers');
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p1] b._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p1] b.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.test.ts:7:7 test2');
});
test('should apply project.grep filter to both _setup and tests', async ({ runGroups }, testInfo) => {
test('should apply project.grep filter to both setup and tests', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
grep: /a.(test|_setup).ts.*(test|_setup)/,
setupMatch: /.*.setup.ts/,
grep: /a.(test|setup).ts.*(test|setup)/,
},
]
};`,
@ -839,65 +839,65 @@ test('should apply project.grep filter to both _setup and tests', async ({ runGr
test('test2', async () => { });
test('foo', async () => { });
`,
'a._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('_setup2', async () => { });
'a.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('setup2', async () => { });
`,
'b._setup.ts': `
const { _setup } = pwt;
_setup('_setup1', async () => { });
_setup('foo', async () => { });
'b.setup.ts': `
const { setup } = pwt;
setup('setup1', async () => { });
setup('foo', async () => { });
`,
};
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(0);
expect(output).toContain('[p1] a._setup.ts:5:7 _setup1');
expect(output).toContain('[p1] a._setup.ts:6:7 _setup2');
expect(output).toContain('[p1] a.setup.ts:5:7 setup1');
expect(output).toContain('[p1] a.setup.ts:6:7 setup2');
expect(output).toContain('[p1] a.test.ts:6:7 test1');
expect(output).toContain('[p1] a.test.ts:7:7 test2');
});
test('should prohibit _setup in test files', async ({ runGroups }, testInfo) => {
test('should prohibit setup in test files', async ({ runGroups }, testInfo) => {
const files = {
'a.test.ts': `
const { _setup, test } = pwt;
_setup('test1', async () => { });
const { setup, test } = pwt;
setup('test1', async () => { });
test('test2', async () => { });
`,
};
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(1);
expect(output).toContain('_setup() is called in a file which is not a part of project setup.');
expect(output).toContain('setup() is called in a file which is not a part of project setup.');
});
test('should prohibit _setup hooks in test files', async ({ runGroups }, testInfo) => {
test('should prohibit setup hooks in test files', async ({ runGroups }, testInfo) => {
const files = {
'a.test.ts': `
const { _setup } = pwt;
_setup.beforeAll(async () => { });
const { setup } = pwt;
setup.beforeAll(async () => { });
`,
};
const { exitCode, output } = await runGroups(files);
expect(exitCode).toBe(1);
expect(output).toContain('_setup.beforeAll() is called in a file which is not a part of project setup');
expect(output).toContain('setup.beforeAll() is called in a file which is not a part of project setup');
});
test('should prohibit test in _setup files', async ({ runGroups }, testInfo) => {
test('should prohibit test in setup files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
'a._setup.ts': `
'a.setup.ts': `
const { test } = pwt;
test('test1', async () => { });
`,
@ -908,18 +908,18 @@ test('should prohibit test in _setup files', async ({ runGroups }, testInfo) =>
expect(output).toContain('test() is called in a project setup file');
});
test('should prohibit test hooks in _setup files', async ({ runGroups }, testInfo) => {
test('should prohibit test hooks in setup files', async ({ runGroups }, testInfo) => {
const files = {
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*._setup.ts/,
setupMatch: /.*.setup.ts/,
},
]
};`,
'a._setup.ts': `
'a.setup.ts': `
const { test } = pwt;
test.beforeEach(async () => { });
`,

Просмотреть файл

@ -16,24 +16,24 @@
import { expect, test } from './playwright-test-fixtures';
test('should provide _store fixture', async ({ runInlineTest }) => {
test('should provide store fixture', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {};
`,
'a.test.ts': `
const { test, _store } = pwt;
test('should _store number', async ({ }) => {
expect(_store).toBeTruthy();
expect(await _store.get('number')).toBe(undefined);
await _store.set('number', 2022)
expect(await _store.get('number')).toBe(2022);
const { test, store } = pwt;
test('should store number', async ({ }) => {
expect(store).toBeTruthy();
expect(await store.get('number')).toBe(undefined);
await store.set('number', 2022)
expect(await store.get('number')).toBe(2022);
});
test('should _store object', async ({ }) => {
expect(_store).toBeTruthy();
expect(await _store.get('object')).toBe(undefined);
await _store.set('object', { 'a': 2022 })
expect(await _store.get('object')).toEqual({ 'a': 2022 });
test('should store object', async ({ }) => {
expect(store).toBeTruthy();
expect(await store.get('object')).toBe(undefined);
await store.set('object', { 'a': 2022 })
expect(await store.get('object')).toEqual({ 'a': 2022 });
});
`,
}, { workers: 1 });
@ -41,42 +41,42 @@ test('should provide _store fixture', async ({ runInlineTest }) => {
expect(result.passed).toBe(2);
});
test('should share _store state between project setup and tests', async ({ runInlineTest }) => {
test('should share store state between project setup and tests', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*_store.setup.ts/
setupMatch: /.*store.setup.ts/
}
]
};
`,
'_store.setup.ts': `
const { _setup, expect, _store } = pwt;
_setup('should initialize _store', async ({ }) => {
expect(await _store.get('number')).toBe(undefined);
await _store.set('number', 2022)
expect(await _store.get('number')).toBe(2022);
'store.setup.ts': `
const { setup, expect, store } = pwt;
setup('should initialize store', async ({ }) => {
expect(await store.get('number')).toBe(undefined);
await store.set('number', 2022)
expect(await store.get('number')).toBe(2022);
expect(await _store.get('object')).toBe(undefined);
await _store.set('object', { 'a': 2022 })
expect(await _store.get('object')).toEqual({ 'a': 2022 });
expect(await store.get('object')).toBe(undefined);
await store.set('object', { 'a': 2022 })
expect(await store.get('object')).toEqual({ 'a': 2022 });
});
`,
'a.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should get data from setup', async ({ }) => {
expect(await _store.get('number')).toBe(2022);
expect(await _store.get('object')).toEqual({ 'a': 2022 });
expect(await store.get('number')).toBe(2022);
expect(await store.get('object')).toEqual({ 'a': 2022 });
});
`,
'b.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should get data from setup', async ({ }) => {
expect(await _store.get('number')).toBe(2022);
expect(await _store.get('object')).toEqual({ 'a': 2022 });
expect(await store.get('number')).toBe(2022);
expect(await store.get('object')).toEqual({ 'a': 2022 });
});
`,
}, { workers: 1 });
@ -84,25 +84,25 @@ test('should share _store state between project setup and tests', async ({ runIn
expect(result.passed).toBe(3);
});
test('should persist _store state between project runs', async ({ runInlineTest }) => {
test('should persist store state between project runs', async ({ runInlineTest }) => {
const files = {
'playwright.config.js': `
module.exports = { };
`,
'a.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should have no data on first run', async ({ }) => {
expect(await _store.get('number')).toBe(undefined);
await _store.set('number', 2022)
expect(await _store.get('object')).toBe(undefined);
await _store.set('object', { 'a': 2022 })
expect(await store.get('number')).toBe(undefined);
await store.set('number', 2022)
expect(await store.get('object')).toBe(undefined);
await store.set('object', { 'a': 2022 })
});
`,
'b.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should get data from previous run', async ({ }) => {
expect(await _store.get('number')).toBe(2022);
expect(await _store.get('object')).toEqual({ 'a': 2022 });
expect(await store.get('number')).toBe(2022);
expect(await store.get('object')).toEqual({ 'a': 2022 });
});
`,
};
@ -118,46 +118,46 @@ test('should persist _store state between project runs', async ({ runInlineTest
}
});
test('should isolate _store state between projects', async ({ runInlineTest }) => {
test('should isolate store state between projects', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
projects: [
{
name: 'p1',
_setupMatch: /.*_store.setup.ts/
setupMatch: /.*store.setup.ts/
},
{
name: 'p2',
_setupMatch: /.*_store.setup.ts/
setupMatch: /.*store.setup.ts/
}
]
};
`,
'_store.setup.ts': `
const { _setup, expect, _store } = pwt;
_setup('should initialize _store', async ({ }) => {
expect(await _store.get('number')).toBe(undefined);
await _store.set('number', 2022)
expect(await _store.get('number')).toBe(2022);
'store.setup.ts': `
const { setup, expect, store } = pwt;
setup('should initialize store', async ({ }) => {
expect(await store.get('number')).toBe(undefined);
await store.set('number', 2022)
expect(await store.get('number')).toBe(2022);
expect(await _store.get('name')).toBe(undefined);
await _store.set('name', 'str-' + _setup.info().project.name)
expect(await _store.get('name')).toBe('str-' + _setup.info().project.name);
expect(await store.get('name')).toBe(undefined);
await store.set('name', 'str-' + setup.info().project.name)
expect(await store.get('name')).toBe('str-' + setup.info().project.name);
});
`,
'a.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should get data from setup', async ({ }) => {
expect(await _store.get('number')).toBe(2022);
expect(await _store.get('name')).toBe('str-' + test.info().project.name);
expect(await store.get('number')).toBe(2022);
expect(await store.get('name')).toBe('str-' + test.info().project.name);
});
`,
'b.test.ts': `
const { test, _store } = pwt;
const { test, store } = pwt;
test('should get data from setup', async ({ }) => {
expect(await _store.get('number')).toBe(2022);
expect(await _store.get('name')).toBe('str-' + test.info().project.name);
expect(await store.get('number')).toBe(2022);
expect(await store.get('name')).toBe('str-' + test.info().project.name);
});
`,
}, { workers: 2 });
@ -165,7 +165,7 @@ test('should isolate _store state between projects', async ({ runInlineTest }) =
expect(result.passed).toBe(6);
});
test('should load context storageState from _store', async ({ runInlineTest, server }) => {
test('should load context storageState from store', async ({ runInlineTest, server }) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', ['a=v1']);
res.end();
@ -176,24 +176,24 @@ test('should load context storageState from _store', async ({ runInlineTest, ser
projects: [
{
name: 'p1',
_setupMatch: /.*_store.setup.ts/
setupMatch: /.*store.setup.ts/
}
]
};
`,
'_store.setup.ts': `
const { _setup, expect, _store } = pwt;
_setup('should save storageState', async ({ page, context }) => {
expect(await _store.get('user')).toBe(undefined);
'store.setup.ts': `
const { setup, expect, store } = pwt;
setup('should save storageState', async ({ page, context }) => {
expect(await store.get('user')).toBe(undefined);
await page.goto('${server.PREFIX}/setcookie.html');
const state = await page.context().storageState();
await _store.set('user', state);
await store.set('user', state);
});
`,
'a.test.ts': `
const { test } = pwt;
test.use({
_storageStateName: 'user'
storageStateName: 'user'
})
test('should get data from setup', async ({ page }) => {
await page.goto('${server.EMPTY_PAGE}');
@ -214,7 +214,7 @@ test('should load context storageState from _store', async ({ runInlineTest, ser
expect(result.passed).toBe(3);
});
test('should load _storageStateName specified in the project config from _store', async ({ runInlineTest, server }) => {
test('should load storageStateName specified in the project config from store', async ({ runInlineTest, server }) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', ['a=v1']);
res.end();
@ -225,24 +225,24 @@ test('should load _storageStateName specified in the project config from _store'
projects: [
{
name: 'p1',
_setupMatch: /.*_store.setup.ts/,
setupMatch: /.*store.setup.ts/,
use: {
_storageStateName: 'stateInStorage',
storageStateName: 'stateInStorage',
},
}
]
};
`,
'_store.setup.ts': `
const { _setup, expect, _store } = pwt;
_setup.use({
_storageStateName: ({}, use) => use(undefined),
'store.setup.ts': `
const { setup, expect, store } = pwt;
setup.use({
storageStateName: ({}, use) => use(undefined),
})
_setup('should save storageState', async ({ page, context }) => {
expect(await _store.get('stateInStorage')).toBe(undefined);
setup('should save storageState', async ({ page, context }) => {
expect(await store.get('stateInStorage')).toBe(undefined);
await page.goto('${server.PREFIX}/setcookie.html');
const state = await page.context().storageState();
await _store.set('stateInStorage', state);
await store.set('stateInStorage', state);
});
`,
'a.test.ts': `
@ -258,7 +258,7 @@ test('should load _storageStateName specified in the project config from _store'
expect(result.passed).toBe(2);
});
test('should load _storageStateName specified in the global config from _store', async ({ runInlineTest, server }) => {
test('should load storageStateName specified in the global config from store', async ({ runInlineTest, server }) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', ['a=v1']);
res.end();
@ -267,26 +267,26 @@ test('should load _storageStateName specified in the global config from _store',
'playwright.config.js': `
module.exports = {
use: {
_storageStateName: 'stateInStorage',
storageStateName: 'stateInStorage',
},
projects: [
{
name: 'p1',
_setupMatch: /.*_store.setup.ts/,
setupMatch: /.*store.setup.ts/,
}
]
};
`,
'_store.setup.ts': `
const { _setup, expect, _store } = pwt;
_setup.use({
_storageStateName: ({}, use) => use(undefined),
'store.setup.ts': `
const { setup, expect, store } = pwt;
setup.use({
storageStateName: ({}, use) => use(undefined),
})
_setup('should save _storageStateName', async ({ page, context }) => {
expect(await _store.get('stateInStorage')).toBe(undefined);
setup('should save storageStateName', async ({ page, context }) => {
expect(await store.get('stateInStorage')).toBe(undefined);
await page.goto('${server.PREFIX}/setcookie.html');
const state = await page.context().storageState();
await _store.set('stateInStorage', state);
await store.set('stateInStorage', state);
});
`,
'a.test.ts': `
@ -302,7 +302,7 @@ test('should load _storageStateName specified in the global config from _store',
expect(result.passed).toBe(2);
});
test('should throw on unknown _storageStateName value', async ({ runInlineTest, server }) => {
test('should throw on unknown storageStateName value', async ({ runInlineTest, server }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
@ -310,7 +310,7 @@ test('should throw on unknown _storageStateName value', async ({ runInlineTest,
{
name: 'p1',
use: {
_storageStateName: 'stateInStorage',
storageStateName: 'stateInStorage',
},
}
]
@ -324,5 +324,5 @@ test('should throw on unknown _storageStateName value', async ({ runInlineTest,
}, { workers: 1 });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('Error: Cannot find value in the _store for _storageStateName: "stateInStorage"');
expect(result.output).toContain('Error: Cannot find value in the store for storageStateName: "stateInStorage"');
});

Просмотреть файл

@ -188,3 +188,18 @@ test('config should allow void/empty options', async ({ runTSC }) => {
});
expect(result.exitCode).toBe(0);
});
test('should provide store interface', async ({ runTSC }) => {
const result = await runTSC({
'a.spec.ts': `
const { test, store } = pwt;
test('my test', async () => {
await store.set('foo', 'bar');
const val = await store.get('foo');
// @ts-expect-error
await store.unknown();
});
`
});
expect(result.exitCode).toBe(0);
});

7
utils/generate_types/overrides-test.d.ts поставляемый
Просмотреть файл

@ -196,6 +196,11 @@ type ConnectOptions = {
timeout?: number;
};
export interface TestStore {
get<T>(name: string): Promise<T | undefined>;
set<T>(name: string, value: T | undefined): Promise<void>;
}
export interface PlaywrightWorkerOptions {
browserName: BrowserName;
defaultBrowserType: BrowserName;
@ -229,6 +234,7 @@ export interface PlaywrightTestOptions {
permissions: string[] | undefined;
proxy: Proxy | undefined;
storageState: StorageState | undefined;
storageStateName: string | undefined;
timezoneId: string | undefined;
userAgent: string | undefined;
viewport: ViewportSize | null | undefined;
@ -351,6 +357,7 @@ export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect;
export const store: TestStore;
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};