From f9ef18912d37247de443b223da053f4bdca27697 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 5 Dec 2022 18:15:01 -0800 Subject: [PATCH] feat: use setup() instead of test() for project setup (#19281) --- packages/playwright-test/src/dispatcher.ts | 2 + packages/playwright-test/src/index.ts | 10 +- packages/playwright-test/src/ipc.ts | 1 + packages/playwright-test/src/loader.ts | 3 +- packages/playwright-test/src/runner.ts | 9 +- packages/playwright-test/src/test.ts | 3 + packages/playwright-test/src/testType.ts | 28 ++- packages/playwright-test/src/workerRunner.ts | 2 +- tests/playwright-test/project-setup.spec.ts | 233 ++++++++++++------- tests/playwright-test/storage.spec.ts | 38 +-- 10 files changed, 214 insertions(+), 115 deletions(-) diff --git a/packages/playwright-test/src/dispatcher.ts b/packages/playwright-test/src/dispatcher.ts index 368833ea37..cced1404fd 100644 --- a/packages/playwright-test/src/dispatcher.ts +++ b/packages/playwright-test/src/dispatcher.ts @@ -32,6 +32,7 @@ export type TestGroup = { projectId: string; tests: TestCase[]; watchMode: boolean; + isProjectSetup: boolean; }; type TestResultData = { @@ -567,6 +568,7 @@ class Worker extends EventEmitter { return { testId: test.id, retry: test.results.length }; }), watchMode: testGroup.watchMode, + projectSetup: testGroup.isProjectSetup, }; this.send({ method: 'run', params: runPayload }); } diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 8db72000a1..85f43ef7a4 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -21,9 +21,9 @@ import * as playwrightLibrary from 'playwright-core'; import * as outOfProcess from 'playwright-core/lib/outofprocess'; import { createGuid, debugMode } from 'playwright-core/lib/utils'; import { removeFolders } from 'playwright-core/lib/utils/fileUtils'; -import type { PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, TestType, TraceMode, VideoMode } from '../types/test'; +import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, TestType, TraceMode, VideoMode } from '../types/test'; import type { TestInfoImpl } from './testInfo'; -import { rootTestType } from './testType'; +import { rootTestType, _setProjectSetup } from './testType'; export { expect } from './expect'; export { addRunnerPlugin as _addRunnerPlugin } from './plugins'; export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -53,7 +53,7 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _snapshotSuffix: string; }; -export const test = _baseTest.extend({ +const playwrightFixtures: Fixtures = ({ defaultBrowserType: ['chromium', { scope: 'worker', option: true }], browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker', option: true }], playwright: [async ({ }, use) => { @@ -627,4 +627,8 @@ export function shouldCaptureTrace(traceMode: TraceMode, testInfo: TestInfo) { const kTracingStarted = Symbol('kTracingStarted'); +export const test = _baseTest.extend(playwrightFixtures); +export const setup = _baseTest.extend(playwrightFixtures); +_setProjectSetup(setup, true); + export default test; diff --git a/packages/playwright-test/src/ipc.ts b/packages/playwright-test/src/ipc.ts index 18e397525d..c330cae505 100644 --- a/packages/playwright-test/src/ipc.ts +++ b/packages/playwright-test/src/ipc.ts @@ -96,6 +96,7 @@ export type RunPayload = { file: string; entries: TestEntry[]; watchMode: boolean; + projectSetup: boolean; }; export type DonePayload = { diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index e81a936367..2ea42d2114 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -180,11 +180,12 @@ export class Loader { } } - async loadTestFile(file: string, environment: 'runner' | 'worker') { + async loadTestFile(file: string, environment: 'runner' | 'worker', projectSetup: boolean) { if (cachedFileSuites.has(file)) return cachedFileSuites.get(file)!; const suite = new Suite(path.relative(this._fullConfig.rootDir, file) || path.basename(file), 'file'); suite._requireFile = file; + suite._isProjectSetup = projectSetup; suite.location = { file, line: 0, column: 0 }; setCurrentlyLoadingFileSuite(suite); diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index 6bd73a9709..3e2de667c5 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -280,7 +280,7 @@ export class Runner { const projects = this._collectProjects(options.projectFilter); const { filesByProject, setupFiles } = await this._collectFiles(projects, options.testFileFilters); - let result = await this._createFilteredRootSuite(options, filesByProject, new Set(), !!setupFiles.size); + let result = await this._createFilteredRootSuite(options, filesByProject, new Set(), !!setupFiles.size, setupFiles); if (setupFiles.size) { const allTests = result.rootSuite.allTests(); const tests = allTests.filter(test => !setupFiles.has(test._requireFile)); @@ -289,7 +289,7 @@ export class Runner { // - if the filter also matches some of the setup tests, we'll run only // that maching subset of setup tests. if (tests.length > 0 && tests.length === allTests.length) - result = await this._createFilteredRootSuite(options, filesByProject, setupFiles, false); + result = await this._createFilteredRootSuite(options, filesByProject, setupFiles, false, setupFiles); } fatalErrors.push(...result.fatalErrors); @@ -307,7 +307,7 @@ export class Runner { return { rootSuite, projectSetupGroups, testGroups }; } - private async _createFilteredRootSuite(options: RunOptions, filesByProject: Map, doNotFilterFiles: Set, shouldCloneTests: boolean): Promise<{rootSuite: Suite, fatalErrors: TestError[]}> { + private async _createFilteredRootSuite(options: RunOptions, filesByProject: Map, doNotFilterFiles: Set, shouldCloneTests: boolean, setupFiles: Set): Promise<{rootSuite: Suite, fatalErrors: TestError[]}> { const config = this._loader.fullConfig(); const fatalErrors: TestError[] = []; const allTestFiles = new Set(); @@ -317,7 +317,7 @@ export class Runner { // Add all tests. const preprocessRoot = new Suite('', 'root'); for (const file of allTestFiles) { - const fileSuite = await this._loader.loadTestFile(file, 'runner'); + const fileSuite = await this._loader.loadTestFile(file, 'runner', setupFiles.has(file)); if (fileSuite._loadError) fatalErrors.push(fileSuite._loadError); // We have to clone only if there maybe subsequent calls of this method. @@ -824,6 +824,7 @@ function createTestGroups(projectSuites: Suite[], workers: number): TestGroup[] projectId: test._projectId, tests: [], watchMode: false, + isProjectSetup: test._isProjectSetup, }; }; diff --git a/packages/playwright-test/src/test.ts b/packages/playwright-test/src/test.ts index c1f16db3b6..594b6df42a 100644 --- a/packages/playwright-test/src/test.ts +++ b/packages/playwright-test/src/test.ts @@ -23,6 +23,7 @@ class Base { title: string; _only = false; _requireFile: string = ''; + _isProjectSetup: boolean = false; constructor(title: string) { this.title = title; @@ -120,6 +121,7 @@ export class Suite extends Base implements reporterTypes.Suite { suite._only = this._only; suite.location = this.location; suite._requireFile = this._requireFile; + suite._isProjectSetup = this._isProjectSetup; suite._use = this._use.slice(); suite._hooks = this._hooks.slice(); suite._timeout = this._timeout; @@ -191,6 +193,7 @@ export class TestCase extends Base implements reporterTypes.TestCase { const test = new TestCase(this.title, this.fn, this._testType, this.location); test._only = this._only; test._requireFile = this._requireFile; + test._isProjectSetup = this._isProjectSetup; test.expectedStatus = this.expectedStatus; test.annotations = this.annotations.slice(); test._annotateWithInheritence = this._annotateWithInheritence; diff --git a/packages/playwright-test/src/testType.ts b/packages/playwright-test/src/testType.ts index 2dc003f378..6f3222bd63 100644 --- a/packages/playwright-test/src/testType.ts +++ b/packages/playwright-test/src/testType.ts @@ -26,6 +26,8 @@ const testTypeSymbol = Symbol('testType'); export class TestTypeImpl { readonly fixtures: FixturesWithLocation[]; readonly test: TestType; + // Wether the test is 'setup' which should only be called inside project setup files. + _projectSetup: boolean = false; constructor(fixtures: FixturesWithLocation[]) { this.fixtures = fixtures; @@ -77,14 +79,20 @@ export class TestTypeImpl { ` when one of the dependencies in your package.json depends on @playwright/test.`, ].join('\n')); } + if (this._projectSetup !== suite._isProjectSetup) { + if (this._projectSetup) + throw errorWithLocation(location, `${title} is called in a file which is not a part of project setup.`); + throw errorWithLocation(location, `${title} is called in a project setup file (use 'setup' instead of 'test').`); + } return suite; } private _createTest(type: 'default' | 'only' | 'skip' | 'fixme', location: Location, title: string, fn: Function) { throwIfRunningInsideJest(); - const suite = this._ensureCurrentSuite(location, 'test()'); + const suite = this._ensureCurrentSuite(location, this._projectSetup ? 'setup()' : 'test()'); const test = new TestCase(title, fn, this, location); test._requireFile = suite._requireFile; + test._isProjectSetup = suite._isProjectSetup; suite._addTest(test); if (type === 'only') @@ -101,7 +109,7 @@ export class TestTypeImpl { private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, title: string | Function, fn?: Function) { throwIfRunningInsideJest(); - const suite = this._ensureCurrentSuite(location, 'test.describe()'); + const suite = this._ensureCurrentSuite(location, this._projectSetup ? 'setup.describe()' : 'test.describe()'); if (typeof title === 'function') { fn = title; @@ -110,6 +118,7 @@ export class TestTypeImpl { const child = new Suite(title, 'describe'); child._requireFile = suite._requireFile; + child._isProjectSetup = suite._isProjectSetup; child.location = location; suite._addSuite(child); @@ -135,13 +144,13 @@ export class TestTypeImpl { } private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, fn: Function) { - const suite = this._ensureCurrentSuite(location, `test.${name}()`); + const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.${name}()`); suite._hooks.push({ type: name, fn, location }); } private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) { throwIfRunningInsideJest(); - const suite = this._ensureCurrentSuite(location, `test.describe.configure()`); + const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.describe.configure()`); if (options.timeout !== undefined) suite._timeout = options.timeout; @@ -202,14 +211,14 @@ export class TestTypeImpl { } private _use(location: Location, fixtures: Fixtures) { - const suite = this._ensureCurrentSuite(location, `test.use()`); + const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? 'setup' : 'test'}.use()`); suite._use.push({ fixtures, location }); } private async _step(location: Location, title: string, body: () => Promise): Promise { const testInfo = currentTestInfo(); if (!testInfo) - throw errorWithLocation(location, `test.step() can only be called from a test`); + throw errorWithLocation(location, `${this._projectSetup ? 'setup' : 'test'}.step() can only be called from a test`); const step = testInfo._addStep({ category: 'test.step', title, @@ -254,4 +263,11 @@ function throwIfRunningInsideJest() { } } +export function _setProjectSetup(test: TestType, projectSetup: boolean) { + const testTypeImpl = (test as any)[testTypeSymbol] as TestTypeImpl; + if (!testTypeImpl) + throw new Error(`Argument is not a TestType`); + testTypeImpl._projectSetup = projectSetup; +} + export const rootTestType = new TestTypeImpl([]); diff --git a/packages/playwright-test/src/workerRunner.ts b/packages/playwright-test/src/workerRunner.ts index adfe8b36c3..4017044aa8 100644 --- a/packages/playwright-test/src/workerRunner.ts +++ b/packages/playwright-test/src/workerRunner.ts @@ -169,7 +169,7 @@ export class WorkerRunner extends EventEmitter { let fatalUnknownTestIds; try { await this._loadIfNeeded(); - const fileSuite = await this._loader.loadTestFile(runPayload.file, 'worker'); + const fileSuite = await this._loader.loadTestFile(runPayload.file, 'worker', runPayload.projectSetup); const suite = this._loader.buildFileSuiteForProject(this._project, fileSuite, this._params.repeatEachIndex, test => { if (runPayload.watchMode) { const testResolvedPayload: WatchTestResolvedPayload = { diff --git a/tests/playwright-test/project-setup.spec.ts b/tests/playwright-test/project-setup.spec.ts index 4787aac6ab..3947681fb1 100644 --- a/tests/playwright-test/project-setup.spec.ts +++ b/tests/playwright-test/project-setup.spec.ts @@ -29,8 +29,8 @@ function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTe await new Promise(f => setTimeout(f, 100)); });`; files[`${name}/${name}.setup.ts`] = ` - const { test } = pwt; - test('${name} setup', async () => { + const { setup } = pwt; + setup('${name} setup', async () => { await new Promise(f => setTimeout(f, 100)); });`; } @@ -142,8 +142,8 @@ test('should stop project if setup fails', async ({ runGroups }, testInfo) => { }; const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates); configWithFiles[`a/a.setup.ts`] = ` - const { test, expect } = pwt; - test('a setup', async () => { + const { setup, expect } = pwt; + setup('a setup', async () => { expect(1).toBe(2); });`; @@ -179,9 +179,9 @@ test('should run setup in each project shard', async ({ runGroups }, testInfo) = test('test2', async () => { }); `, 'c.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, }; @@ -233,14 +233,14 @@ test('should run setup only for projects that have tests in the shard', async ({ test('test2', async () => { }); `, 'p1.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'p2.setup.ts': ` - const { test } = pwt; - test('setup3', async () => { }); - test('setup4', async () => { }); + const { setup } = pwt; + setup('setup3', async () => { }); + setup('setup4', async () => { }); `, }; @@ -347,20 +347,20 @@ test('list-files should enumerate setup files in same group', async ({ runComman ] };`, 'a1.setup.ts': ` - const { test } = pwt; - test('test1', async () => { }); + const { setup } = pwt; + setup('test1', async () => { }); `, 'a2.setup.ts': ` - const { test } = pwt; - test('test1', async () => { }); + const { setup } = pwt; + setup('test1', async () => { }); `, 'a.test.ts': ` const { test } = pwt; test('test2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('test3', async () => { }); + const { setup } = pwt; + setup('test3', async () => { }); `, 'b.test.ts': ` const { test } = pwt; @@ -394,20 +394,20 @@ test('test --list should enumerate setup tests as regular ones', async ({ runCom ] };`, 'a1.setup.ts': ` - const { test } = pwt; - test('test1', async () => { }); + const { setup } = pwt; + setup('test1', async () => { }); `, 'a2.setup.ts': ` - const { test } = pwt; - test('test1', async () => { }); + const { setup } = pwt; + setup('test1', async () => { }); `, 'a.test.ts': ` const { test } = pwt; test('test2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('test3', async () => { }); + const { setup } = pwt; + setup('test3', async () => { }); `, 'b.test.ts': ` const { test } = pwt; @@ -445,17 +445,17 @@ test('should allow .only in setup files', async ({ runGroups }, testInfo) => { test('test4', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test.only('setup1', async () => { }); - test('setup2', async () => { }); - test.only('setup3', async () => { }); + 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:12 › setup1'); - expect(output).toContain('[p1] › a.setup.ts:7:12 › setup3'); + 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); @@ -480,12 +480,12 @@ test('should allow describe.only in setup files', async ({ runGroups }, testInfo test('test4', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test.describe.only('main', () => { - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup.describe.only('main', () => { + setup('setup1', async () => { }); + setup('setup2', async () => { }); }); - test('setup3', async () => { }); + setup('setup3', async () => { }); `, }; @@ -517,12 +517,12 @@ test('should filter describe line in setup files', async ({ runGroups }, testInf test('test4', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test.describe('main', () => { - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup.describe('main', () => { + setup('setup1', async () => { }); + setup('setup2', async () => { }); }); - test('setup3', async () => { }); + setup('setup3', async () => { }); `, }; @@ -554,16 +554,16 @@ test('should allow .only in both setup and test files', async ({ runGroups }, te test('test4', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test.only('setup1', async () => { }); - test('setup2', async () => { }); - test('setup3', async () => { }); + 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:12 › setup1'); + expect(output).toContain('[p1] › a.setup.ts:5:13 › setup1'); expect(output).toContain('[p1] › a.test.ts:7:12 › test2'); }); @@ -586,13 +586,13 @@ test('should run full setup when there is test.only', async ({ runGroups }, test test('test4', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup3', async () => { }); + const { setup } = pwt; + setup('setup3', async () => { }); `, }; @@ -628,13 +628,13 @@ test('should allow filtering setup by file:line', async ({ runGroups }, testInfo test('test3', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, 'b.test.ts': ` const { test } = pwt; @@ -680,13 +680,13 @@ test('should support filters matching both setup and test', async ({ runGroups } test('test3', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, 'b.test.ts': ` const { test } = pwt; @@ -726,12 +726,12 @@ test('should run setup for a project if tests match only in another project', as test('test1', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, 'b.test.ts': ` const { test } = pwt; @@ -765,13 +765,13 @@ test('should run all setup files if only tests match filter', async ({ runGroups test('test3', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, }; @@ -802,13 +802,13 @@ test('should run all setup files if only tests match grep filter', async ({ runG test('test3', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); `, }; @@ -840,14 +840,14 @@ test('should apply project.grep filter to both setup and tests', async ({ runGro test('foo', async () => { }); `, 'a.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('setup2', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('setup2', async () => { }); `, 'b.setup.ts': ` - const { test } = pwt; - test('setup1', async () => { }); - test('foo', async () => { }); + const { setup } = pwt; + setup('setup1', async () => { }); + setup('foo', async () => { }); `, }; @@ -858,3 +858,74 @@ test('should apply project.grep filter to both setup and tests', async ({ runGro 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) => { + const files = { + 'a.test.ts': ` + 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.'); +}); + +test('should prohibit setup hooks in test files', async ({ runGroups }, testInfo) => { + const files = { + 'a.test.ts': ` + 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'); +}); + +test('should prohibit test in setup files', async ({ runGroups }, testInfo) => { + const files = { + 'playwright.config.ts': ` + module.exports = { + projects: [ + { + name: 'p1', + setup: /.*.setup.ts/, + }, + ] + };`, + 'a.setup.ts': ` + const { test } = pwt; + test('test1', async () => { }); + `, + }; + + const { exitCode, output } = await runGroups(files); + expect(exitCode).toBe(1); + expect(output).toContain('test() is called in a project setup file'); +}); + +test('should prohibit test hooks in setup files', async ({ runGroups }, testInfo) => { + const files = { + 'playwright.config.ts': ` + module.exports = { + projects: [ + { + name: 'p1', + setup: /.*.setup.ts/, + }, + ] + };`, + 'a.setup.ts': ` + const { test } = pwt; + test.beforeEach(async () => { }); + `, + }; + + const { exitCode, output } = await runGroups(files); + expect(exitCode).toBe(1); + expect(output).toContain('test.beforeEach() is called in a project setup file'); +}); diff --git a/tests/playwright-test/storage.spec.ts b/tests/playwright-test/storage.spec.ts index 28f2f5b51a..caf5e95e13 100644 --- a/tests/playwright-test/storage.spec.ts +++ b/tests/playwright-test/storage.spec.ts @@ -56,9 +56,9 @@ test('should share storage state between project setup and tests', async ({ runI }; `, 'storage.setup.ts': ` - const { test, expect } = pwt; - test('should initialize storage', async ({ }) => { - const storage = test.info().storage(); + const { setup, expect } = pwt; + setup('should initialize storage', async ({ }) => { + const storage = setup.info().storage(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('number')).toBe(2022); @@ -142,16 +142,16 @@ test('should isolate storage state between projects', async ({ runInlineTest }) }; `, 'storage.setup.ts': ` - const { test, expect } = pwt; - test('should initialize storage', async ({ }) => { - const storage = test.info().storage(); + const { setup, expect } = pwt; + setup('should initialize storage', async ({ }) => { + const storage = setup.info().storage(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('number')).toBe(2022); expect(await storage.get('name')).toBe(undefined); - await storage.set('name', 'str-' + test.info().project.name) - expect(await storage.get('name')).toBe('str-' + test.info().project.name); + await storage.set('name', 'str-' + setup.info().project.name) + expect(await storage.get('name')).toBe('str-' + setup.info().project.name); }); `, 'a.test.ts': ` @@ -192,9 +192,9 @@ test('should load context storageState from storage', async ({ runInlineTest, se }; `, 'storage.setup.ts': ` - const { test, expect } = pwt; - test('should save storageState', async ({ page, context }) => { - const storage = test.info().storage(); + const { setup, expect } = pwt; + setup('should save storageState', async ({ page, context }) => { + const storage = setup.info().storage(); expect(await storage.get('user')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState(); @@ -245,12 +245,12 @@ test('should load storageStateName specified in the project config from storage' }; `, 'storage.setup.ts': ` - const { test, expect } = pwt; - test.use({ + const { setup, expect } = pwt; + setup.use({ storageStateName: ({}, use) => use(undefined), }) - test('should save storageState', async ({ page, context }) => { - const storage = test.info().storage(); + setup('should save storageState', async ({ page, context }) => { + const storage = setup.info().storage(); expect(await storage.get('stateInStorage')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState(); @@ -290,12 +290,12 @@ test('should load storageStateName specified in the global config from storage', }; `, 'storage.setup.ts': ` - const { test, expect } = pwt; - test.use({ + const { setup, expect } = pwt; + setup.use({ storageStateName: ({}, use) => use(undefined), }) - test('should save storageStateName', async ({ page, context }) => { - const storage = test.info().storage(); + setup('should save storageStateName', async ({ page, context }) => { + const storage = setup.info().storage(); expect(await storage.get('stateInStorage')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState();