From 137070d889df207c32539355c45144ad8b73cf1c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Dec 2022 15:39:31 -0800 Subject: [PATCH] =?UTF-8?q?Revert=20"chore:=20hide=20setup,=20store,=20Tes?= =?UTF-8?q?tProject.setupMatch,=20storageStat=E2=80=A6=20(#19756)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …eName (#19442)" This reverts commit 92dd734e0471cc1b4724ad965e4389ed5235a9f1. --- docs/src/test-api/class-testoptions.md | 8 + docs/src/test-api/class-testproject.md | 14 +- docs/src/test-api/class-teststore.md | 56 +++ packages/playwright-test/src/index.ts | 17 +- packages/playwright-test/src/loader.ts | 4 +- packages/playwright-test/src/runner.ts | 6 +- packages/playwright-test/src/store.ts | 3 +- packages/playwright-test/src/testType.ts | 12 +- packages/playwright-test/types/test.d.ts | 68 ++- tests/playwright-test/config.spec.ts | 38 ++ tests/playwright-test/project-setup.spec.ts | 452 ++++++++++---------- tests/playwright-test/store.spec.ts | 174 ++++---- tests/playwright-test/types.spec.ts | 15 + utils/generate_types/overrides-test.d.ts | 7 + 14 files changed, 537 insertions(+), 337 deletions(-) create mode 100644 docs/src/test-api/class-teststore.md diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index 2bacc6d10d..a89ff9ddbe 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -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 diff --git a/docs/src/test-api/class-testproject.md b/docs/src/test-api/class-testproject.md index 51588c2c2e..e95348aa0b 100644 --- a/docs/src/test-api/class-testproject.md +++ b/docs/src/test-api/class-testproject.md @@ -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]> diff --git a/docs/src/test-api/class-teststore.md b/docs/src/test-api/class-teststore.md new file mode 100644 index 0000000000..99ad4b0bdd --- /dev/null +++ b/docs/src/test-api/class-teststore.md @@ -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. + diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 5f157fc722..109824483d 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -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; - _storageStateName: string | undefined; }; type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _browserOptions: LaunchOptions; @@ -144,7 +143,7 @@ const playwrightFixtures: Fixtures = ({ 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 = ({ permissions, proxy, storageState, - _storageStateName, + storageStateName, viewport, timezoneId, userAgent, @@ -213,10 +212,10 @@ const playwrightFixtures: Fixtures = ({ 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(playwrightFixtures); -export const _setup = _baseTest.extend(playwrightFixtures); -_setProjectSetup(_setup, true); +export const setup = _baseTest.extend(playwrightFixtures); +_setProjectSetup(setup, true); export default test; diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index 8be3a75758..1906a85c29 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -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)) { diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index dce8918f33..f9b7ca23f2 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -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); diff --git a/packages/playwright-test/src/store.ts b/packages/playwright-test/src/store.ts index cca8d143ad..382ccdc7a3 100644 --- a/packages/playwright-test/src/store.ts +++ b/packages/playwright-test/src/store.ts @@ -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) diff --git a/packages/playwright-test/src/testType.ts b/packages/playwright-test/src/testType.ts index 245d3f3147..2a68e9de77 100644 --- a/packages/playwright-test/src/testType.ts +++ b/packages/playwright-test/src/testType.ts @@ -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(location: Location, title: string, body: () => Promise): Promise { 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({ diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index adc148e432..361230b18b 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -193,7 +193,10 @@ export interface FullProject { /** * 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(name: string): Promise; + /** + * 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(name: string, value: T | undefined): Promise; +} + /** * 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; + /** * 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). diff --git a/tests/playwright-test/config.spec.ts b/tests/playwright-test/config.spec.ts index 23082e0a60..b037f3ab1e 100644 --- a/tests/playwright-test/config.spec.ts +++ b/tests/playwright-test/config.spec.ts @@ -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`); +}); diff --git a/tests/playwright-test/project-setup.spec.ts b/tests/playwright-test/project-setup.spec.ts index 03fb05ce82..c590b98679 100644 --- a/tests/playwright-test/project-setup.spec.ts +++ b/tests/playwright-test/project-setup.spec.ts @@ -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 { +function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTemplates?: { [name: string]: PlaywrightTestProject }): Record { 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 () => { }); `, diff --git a/tests/playwright-test/store.spec.ts b/tests/playwright-test/store.spec.ts index 5d2d8198f0..87a3e18b99 100644 --- a/tests/playwright-test/store.spec.ts +++ b/tests/playwright-test/store.spec.ts @@ -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"'); }); \ No newline at end of file diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index 67d9adb715..7f75860f5e 100644 --- a/tests/playwright-test/types.spec.ts +++ b/tests/playwright-test/types.spec.ts @@ -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); +}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 8f308ee474..8c3d030a7e 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -196,6 +196,11 @@ type ConnectOptions = { timeout?: number; }; +export interface TestStore { + get(name: string): Promise; + set(name: string, value: T | undefined): Promise; +} + 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 {};