diff --git a/docs/src/test-api/class-storage.md b/docs/src/test-api/class-storage.md new file mode 100644 index 0000000000000..366544a33171a --- /dev/null +++ b/docs/src/test-api/class-storage.md @@ -0,0 +1,36 @@ +# class: Storage +* since: v1.28 +* langs: js + +Playwright Test provides a [`method: TestInfo.storage`] object for passing values between project setup and tests. +TODO: examples + +## async method: Storage.get +* since: v1.28 +- returns: <[any]> + +Get named item from the storage. Returns undefined if there is no value with given name. + +### param: Storage.get.name +* since: v1.28 +- `name` <[string]> + +Item name. + +## async method: Storage.set +* since: v1.28 + +Set value to the storage. + +### param: Storage.set.name +* since: v1.28 +- `name` <[string]> + +Item name. + +### param: Storage.set.value +* since: v1.28 +- `value` <[any]> + +Item value. The value must be serializable to JSON. Passing `undefined` deletes the entry with given name. + diff --git a/docs/src/test-api/class-testinfo.md b/docs/src/test-api/class-testinfo.md index 4c9e6e6c18c8f..b545955203707 100644 --- a/docs/src/test-api/class-testinfo.md +++ b/docs/src/test-api/class-testinfo.md @@ -505,6 +505,12 @@ Output written to `process.stderr` or `console.error` during the test execution. Output written to `process.stdout` or `console.log` during the test execution. +## method: TestInfo.storage +* since: v1.28 +- returns: <[Storage]> + +Returns a [Storage] instance for the currently running project. + ## property: TestInfo.timeout * since: v1.10 - type: <[int]> diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index 17da609303466..7181f15d62e48 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -202,6 +202,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.28 +- type: <[string]> + +Name of the [Storage] entry that should be used to initialize [`property: TestOptions.storageState`]. The value must be +written to the storage before creatiion of a browser context that uses it (usually in [`property: TestProject.setup`]). 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 f7b4446b2d90a..5acda1ce41e36 100644 --- a/docs/src/test-api/class-testproject.md +++ b/docs/src/test-api/class-testproject.md @@ -162,6 +162,12 @@ 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.setup +* since: v1.28 +- type: ?<[string]|[RegExp]|[Array]<[string]|[RegExp]>> + +Project setup files that would be executed before all tests in the project. 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.snapshotDir * since: v1.10 - type: ?<[string]> diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index e2bccae35bb13..8db72000a1051 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -49,7 +49,6 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & { }; type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _browserOptions: LaunchOptions; - _storageStateName: string | undefined; _artifactsDir: () => string; _snapshotSuffix: string; }; @@ -152,7 +151,7 @@ export const test = _baseTest.extend({ 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, scope: 'worker' }], + 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 }], @@ -182,7 +181,7 @@ export const test = _baseTest.extend({ permissions, proxy, storageState, - _storageStateName, + storageStateName, viewport, timezoneId, userAgent, @@ -221,10 +220,10 @@ export const test = _baseTest.extend({ options.permissions = permissions; if (proxy !== undefined) options.proxy = proxy; - if (_storageStateName !== undefined) { - const value = await (test.info() as TestInfoImpl)._storage().get(_storageStateName); + if (storageStateName !== undefined) { + const value = await test.info().storage().get(storageStateName); if (!value) - throw new Error(`Cannot find value in the storage for storageStateName: "${_storageStateName}"`); + throw new Error(`Cannot find value in the storage for storageStateName: "${storageStateName}"`); options.storageState = value as any; } else if (storageState !== undefined) { options.storageState = storageState; diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index 4d1da3c5e02a1..e81a936367309 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -274,7 +274,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 _setup = takeFirst((projectConfig as any)._setup, []); + const _setup = takeFirst(projectConfig.setup, []); 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', 'setup'] as const) { if (prop in project && project[prop] !== undefined) { const value = project[prop]; if (Array.isArray(value)) { diff --git a/packages/playwright-test/src/testInfo.ts b/packages/playwright-test/src/testInfo.ts index 813a080dfac3d..a582b8fd1dd94 100644 --- a/packages/playwright-test/src/testInfo.ts +++ b/packages/playwright-test/src/testInfo.ts @@ -17,7 +17,7 @@ import fs from 'fs'; import path from 'path'; import { monotonicTime } from 'playwright-core/lib/utils'; -import type { TestError, TestInfo, TestStatus } from '../types/test'; +import type { Storage, TestError, TestInfo, TestStatus } from '../types/test'; import type { WorkerInitParams } from './ipc'; import type { Loader } from './loader'; import type { TestCase } from './test'; @@ -60,7 +60,7 @@ export class TestInfoImpl implements TestInfo { readonly snapshotDir: string; errors: TestError[] = []; currentStep: TestStepInternal | undefined; - private readonly _testStorage: JsonStorage; + private readonly _storage: JsonStorage; get error(): TestError | undefined { return this.errors[0]; @@ -108,7 +108,7 @@ export class TestInfoImpl implements TestInfo { this.expectedStatus = test.expectedStatus; this._timeoutManager = new TimeoutManager(this.project.timeout); - this._testStorage = new JsonStorage(this); + this._storage = new JsonStorage(this); this.outputDir = (() => { const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile.replace(/\.(spec|test)\.(js|ts|mjs)$/, '')); @@ -281,12 +281,12 @@ export class TestInfoImpl implements TestInfo { this._timeoutManager.setTimeout(timeout); } - _storage() { - return this._testStorage; + storage() { + return this._storage; } } -class JsonStorage { +class JsonStorage implements Storage { constructor(private _testInfo: TestInfoImpl) { } diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index 08d6a3df0dfed..e61b28b00f2e9 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -1849,6 +1849,11 @@ export interface TestInfo { */ stdout: Array; + /** + * Returns a [Storage] instance for the currently running project. + */ + storage(): Storage; + /** * Timeout in milliseconds for the currently running test. Zero means no timeout. Learn more about * [various timeouts](https://playwright.dev/docs/test-timeouts). @@ -2777,6 +2782,24 @@ type ConnectOptions = { timeout?: number; }; +/** + * Playwright Test provides a [testInfo.storage()](https://playwright.dev/docs/api/class-testinfo#test-info-storage) object + * for passing values between project setup and tests. TODO: examples + */ +export interface Storage { + /** + * Get named item from the storage. Returns undefined if there is no value with given name. + * @param name Item name. + */ + get(name: string): Promise; + /** + * Set value to the storage. + * @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. * @@ -3011,6 +3034,15 @@ 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 [Storage] 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 storage before creatiion of a browser context that uses it (usually in + * [testProject.setup](https://playwright.dev/docs/api/class-testproject#test-project-setup)). 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) @@ -4523,6 +4555,12 @@ interface TestProject { */ name?: string; + /** + * Project setup files that would be executed before all tests in the project. 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. + */ + setup?: 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 23082e0a60af9..931c806174613 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.setup has wrong type', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { + projects: [ + { name: 'a', setup: 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].setup must be a string or a RegExp`); +}); + +test('should throw when project.setup has wrong array type', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { + projects: [ + { name: 'a', setup: [/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].setup[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 be69200e670bf..55044ed13d6b3 100644 --- a/tests/playwright-test/project-setup.spec.ts +++ b/tests/playwright-test/project-setup.spec.ts @@ -98,7 +98,7 @@ function expectFilesRunBefore(timeline: Timeline, before: string[], after: strin test('should work for one project', async ({ runGroups }, testInfo) => { const projectTemplates = { 'a': { - _setup: ['**/*.setup.ts'] + setup: ['**/*.setup.ts'] }, }; const configWithFiles = createConfigWithProjects(['a'], testInfo, projectTemplates); @@ -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': { - _setup: ['**/*.setup.ts'] + setup: ['**/*.setup.ts'] }, 'b': { - _setup: /.*b.setup.ts/ + setup: /.*b.setup.ts/ }, 'c': { - _setup: '**/c.setup.ts' + setup: '**/c.setup.ts' }, }; const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates); @@ -134,10 +134,10 @@ test('should work for several projects', async ({ runGroups }, testInfo) => { test('should stop project if setup fails', async ({ runGroups }, testInfo) => { const projectTemplates = { 'a': { - _setup: ['**/*.setup.ts'] + setup: ['**/*.setup.ts'] }, 'b': { - _setup: /.*b.setup.ts/ + setup: /.*b.setup.ts/ }, }; const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates); @@ -162,7 +162,7 @@ test('should run setup in each project shard', async ({ runGroups }, testInfo) = projects: [ { name: 'p1', - _setup: /.*.setup.ts/, + setup: /.*.setup.ts/, }, ] };`, @@ -210,12 +210,12 @@ test('should run setup only for projects that have tests in the shard', async ({ projects: [ { name: 'p1', - _setup: /.*p1.setup.ts$/, + setup: /.*p1.setup.ts$/, testMatch: /.*a.test.ts/, }, { name: 'p2', - _setup: /.*p2.setup.ts$/, + setup: /.*p2.setup.ts$/, testMatch: /.*b.test.ts/, }, ] @@ -265,10 +265,10 @@ test('should run setup only for projects that have tests in the shard', async ({ test('--project only runs setup from that project;', async ({ runGroups }, testInfo) => { const projectTemplates = { 'a': { - _setup: /.*a.setup.ts/ + setup: /.*a.setup.ts/ }, 'b': { - _setup: /.*b.setup.ts/ + setup: /.*b.setup.ts/ }, }; const configWithFiles = createConfigWithProjects(['a', 'b', 'c'], testInfo, projectTemplates); @@ -285,7 +285,7 @@ test('same file cannot be a setup and a test in the same project', async ({ runG projects: [ { name: 'p1', - _setup: /.*a.test.ts$/, + setup: /.*a.test.ts$/, testMatch: /.*a.test.ts$/, }, ] @@ -308,12 +308,12 @@ test('same file cannot be a setup and a test in different projects', async ({ ru projects: [ { name: 'p1', - _setup: /.*a.test.ts$/, + setup: /.*a.test.ts$/, testMatch: /.*noMatch.test.ts$/, }, { name: 'p2', - _setup: /.*noMatch.test.ts$/, + setup: /.*noMatch.test.ts$/, testMatch: /.*a.test.ts$/ }, ] diff --git a/tests/playwright-test/storage.spec.ts b/tests/playwright-test/storage.spec.ts index d60b7d1557d2a..28f2f5b51a79c 100644 --- a/tests/playwright-test/storage.spec.ts +++ b/tests/playwright-test/storage.spec.ts @@ -24,14 +24,14 @@ test('should provide storage fixture', async ({ runInlineTest }) => { 'a.test.ts': ` const { test } = pwt; test('should store number', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(storage).toBeTruthy(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('number')).toBe(2022); }); test('should store object', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(storage).toBeTruthy(); expect(await storage.get('object')).toBe(undefined); await storage.set('object', { 'a': 2022 }) @@ -50,7 +50,7 @@ test('should share storage state between project setup and tests', async ({ runI projects: [ { name: 'p1', - _setup: /.*storage.setup.ts/ + setup: /.*storage.setup.ts/ } ] }; @@ -58,7 +58,7 @@ 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 storage = test.info().storage(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('number')).toBe(2022); @@ -71,7 +71,7 @@ test('should share storage state between project setup and tests', async ({ runI 'a.test.ts': ` const { test } = pwt; test('should get data from setup', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(2022); expect(await storage.get('object')).toEqual({ 'a': 2022 }); }); @@ -79,7 +79,7 @@ test('should share storage state between project setup and tests', async ({ runI 'b.test.ts': ` const { test } = pwt; test('should get data from setup', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(2022); expect(await storage.get('object')).toEqual({ 'a': 2022 }); }); @@ -97,7 +97,7 @@ test('should persist storage state between project runs', async ({ runInlineTest 'a.test.ts': ` const { test } = pwt; test('should have no data on first run', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('object')).toBe(undefined); @@ -107,7 +107,7 @@ test('should persist storage state between project runs', async ({ runInlineTest 'b.test.ts': ` const { test } = pwt; test('should get data from previous run', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(2022); expect(await storage.get('object')).toEqual({ 'a': 2022 }); }); @@ -132,11 +132,11 @@ test('should isolate storage state between projects', async ({ runInlineTest }) projects: [ { name: 'p1', - _setup: /.*storage.setup.ts/ + setup: /.*storage.setup.ts/ }, { name: 'p2', - _setup: /.*storage.setup.ts/ + setup: /.*storage.setup.ts/ } ] }; @@ -144,7 +144,7 @@ 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 storage = test.info().storage(); expect(await storage.get('number')).toBe(undefined); await storage.set('number', 2022) expect(await storage.get('number')).toBe(2022); @@ -157,7 +157,7 @@ test('should isolate storage state between projects', async ({ runInlineTest }) 'a.test.ts': ` const { test } = pwt; test('should get data from setup', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(2022); expect(await storage.get('name')).toBe('str-' + test.info().project.name); }); @@ -165,7 +165,7 @@ test('should isolate storage state between projects', async ({ runInlineTest }) 'b.test.ts': ` const { test } = pwt; test('should get data from setup', async ({ }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('number')).toBe(2022); expect(await storage.get('name')).toBe('str-' + test.info().project.name); }); @@ -186,7 +186,7 @@ test('should load context storageState from storage', async ({ runInlineTest, se projects: [ { name: 'p1', - _setup: /.*storage.setup.ts/ + setup: /.*storage.setup.ts/ } ] }; @@ -194,7 +194,7 @@ 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 storage = test.info().storage(); expect(await storage.get('user')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState(); @@ -204,7 +204,7 @@ test('should load context storageState from storage', async ({ runInlineTest, se '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}'); @@ -225,7 +225,7 @@ test('should load context storageState from storage', async ({ runInlineTest, se expect(result.passed).toBe(3); }); -test('should load _storageStateName specified in the project config from storage', async ({ runInlineTest, server }) => { +test('should load storageStateName specified in the project config from storage', async ({ runInlineTest, server }) => { server.setRoute('/setcookie.html', (req, res) => { res.setHeader('Set-Cookie', ['a=v1']); res.end(); @@ -236,9 +236,9 @@ test('should load _storageStateName specified in the project config from storage projects: [ { name: 'p1', - _setup: /.*storage.setup.ts/, + setup: /.*storage.setup.ts/, use: { - _storageStateName: 'stateInStorage', + storageStateName: 'stateInStorage', }, } ] @@ -247,10 +247,10 @@ test('should load _storageStateName specified in the project config from storage 'storage.setup.ts': ` const { test, expect } = pwt; test.use({ - _storageStateName: ({}, use) => use(undefined), + storageStateName: ({}, use) => use(undefined), }) test('should save storageState', async ({ page, context }) => { - const storage = test.info()._storage(); + const storage = test.info().storage(); expect(await storage.get('stateInStorage')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState(); @@ -270,7 +270,7 @@ test('should load _storageStateName specified in the project config from storage expect(result.passed).toBe(2); }); -test('should load _storageStateName specified in the global config from storage', async ({ runInlineTest, server }) => { +test('should load storageStateName specified in the global config from storage', async ({ runInlineTest, server }) => { server.setRoute('/setcookie.html', (req, res) => { res.setHeader('Set-Cookie', ['a=v1']); res.end(); @@ -279,12 +279,12 @@ test('should load _storageStateName specified in the global config from storage' 'playwright.config.js': ` module.exports = { use: { - _storageStateName: 'stateInStorage', + storageStateName: 'stateInStorage', }, projects: [ { name: 'p1', - _setup: /.*storage.setup.ts/, + setup: /.*storage.setup.ts/, } ] }; @@ -292,10 +292,10 @@ test('should load _storageStateName specified in the global config from storage' 'storage.setup.ts': ` const { test, expect } = pwt; test.use({ - _storageStateName: ({}, use) => use(undefined), + storageStateName: ({}, use) => use(undefined), }) - test('should save _storageStateName', async ({ page, context }) => { - const storage = test.info()._storage(); + test('should save storageStateName', async ({ page, context }) => { + const storage = test.info().storage(); expect(await storage.get('stateInStorage')).toBe(undefined); await page.goto('${server.PREFIX}/setcookie.html'); const state = await page.context().storageState(); @@ -315,7 +315,7 @@ test('should load _storageStateName specified in the global config from storage' 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 = { @@ -323,7 +323,7 @@ test('should throw on unknown _storageStateName value', async ({ runInlineTest, { name: 'p1', use: { - _storageStateName: 'stateInStorage', + storageStateName: 'stateInStorage', }, } ] diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index 67d9adb715770..1ef96945f9c8c 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 storage interface', async ({ runTSC }) => { + const result = await runTSC({ + 'a.spec.ts': ` + const { test } = pwt; + test('my test', async () => { + await test.info().storage().set('foo', 'bar'); + const val = await test.info().storage().get('foo'); + // @ts-expect-error + await test.info().storage().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 5a41fa4de322c..ad666c606a2cf 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 Storage { + get(name: string): Promise; + set(name: string, value: T | undefined): Promise; +} + export interface PlaywrightWorkerOptions { browserName: BrowserName; defaultBrowserType: BrowserName; @@ -228,6 +233,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;