feat(runner): storage fixture (#18522)
This commit is contained in:
parent
20f2e0049c
commit
45aa82242d
|
|
@ -128,3 +128,9 @@ test('basic test', async ({ request }) => {
|
||||||
// ...
|
// ...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## property: Fixtures.storage
|
||||||
|
* since: v1.28
|
||||||
|
- type: <[Storage]>
|
||||||
|
|
||||||
|
[Storage] is shared between all tests in the same run.
|
||||||
|
|
|
||||||
36
docs/src/test-api/class-storage.md
Normal file
36
docs/src/test-api/class-storage.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# class: Storage
|
||||||
|
* since: v1.28
|
||||||
|
* langs: js
|
||||||
|
|
||||||
|
Playwright Test provides a `storage` fixture for passing values between project setup and tests.
|
||||||
|
TODO: examples
|
||||||
|
|
||||||
|
## method: Storage.get
|
||||||
|
* since: v1.28
|
||||||
|
- returns: <[any]>
|
||||||
|
|
||||||
|
Get named item from the store.
|
||||||
|
|
||||||
|
### param: Storage.get.name
|
||||||
|
* since: v1.28
|
||||||
|
- `name` <[string]>
|
||||||
|
|
||||||
|
Item name.
|
||||||
|
|
||||||
|
## method: Storage.set
|
||||||
|
* since: v1.28
|
||||||
|
|
||||||
|
Set value to the store.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
|
@ -16,17 +16,18 @@
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { LaunchOptions, BrowserContextOptions, Page, BrowserContext, Video, APIRequestContext, Tracing } from 'playwright-core';
|
import type { APIRequestContext, BrowserContext, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core';
|
||||||
import type { TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, VideoMode, TraceMode } from '../types/test';
|
import * as playwrightLibrary from 'playwright-core';
|
||||||
import { rootTestType } from './testType';
|
import * as outOfProcess from 'playwright-core/lib/outofprocess';
|
||||||
import { createGuid, debugMode } from 'playwright-core/lib/utils';
|
import { createGuid, debugMode } from 'playwright-core/lib/utils';
|
||||||
import { removeFolders } from 'playwright-core/lib/utils/fileUtils';
|
import { removeFolders } from 'playwright-core/lib/utils/fileUtils';
|
||||||
export { expect } from './expect';
|
import type { PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, TestType, TraceMode, VideoMode } from '../types/test';
|
||||||
export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
|
||||||
export { addRunnerPlugin as _addRunnerPlugin } from './plugins';
|
|
||||||
import * as outOfProcess from 'playwright-core/lib/outofprocess';
|
|
||||||
import * as playwrightLibrary from 'playwright-core';
|
|
||||||
import type { TestInfoImpl } from './testInfo';
|
import type { TestInfoImpl } from './testInfo';
|
||||||
|
import { rootTestType } from './testType';
|
||||||
|
import { sanitizeForFilePath, trimLongString } from './util';
|
||||||
|
export { expect } from './expect';
|
||||||
|
export { addRunnerPlugin as _addRunnerPlugin } from './plugins';
|
||||||
|
export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
||||||
|
|
||||||
if ((process as any)['__pw_initiator__']) {
|
if ((process as any)['__pw_initiator__']) {
|
||||||
const originalStackTraceLimit = Error.stackTraceLimit;
|
const originalStackTraceLimit = Error.stackTraceLimit;
|
||||||
|
|
@ -127,7 +128,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
}
|
}
|
||||||
}, { scope: 'worker', auto: true }],
|
}, { scope: 'worker', auto: true }],
|
||||||
|
|
||||||
browser: [async ({ playwright, browserName }, use) => {
|
browser: [async ({ playwright, browserName }, use, testInfo) => {
|
||||||
if (!['chromium', 'firefox', 'webkit'].includes(browserName))
|
if (!['chromium', 'firefox', 'webkit'].includes(browserName))
|
||||||
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
||||||
const browser = await playwright[browserName].launch();
|
const browser = await playwright[browserName].launch();
|
||||||
|
|
@ -135,6 +136,35 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
await browser.close();
|
await browser.close();
|
||||||
}, { scope: 'worker', timeout: 0 }],
|
}, { scope: 'worker', timeout: 0 }],
|
||||||
|
|
||||||
|
storage: [async ({ }, use, testInfo) => {
|
||||||
|
const toFilePath = (name: string) => {
|
||||||
|
const fileName = sanitizeForFilePath(trimLongString(name)) + '.json';
|
||||||
|
return path.join(testInfo.project.outputDir, '.playwright-storage', (testInfo as TestInfoImpl).project._id, fileName);
|
||||||
|
};
|
||||||
|
const storage = {
|
||||||
|
async get<T>(name: string) {
|
||||||
|
const file = toFilePath(name);
|
||||||
|
try {
|
||||||
|
const data = (await fs.promises.readFile(file)).toString('utf-8');
|
||||||
|
return JSON.parse(data) as T;
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async set<T>(name: string, value: T | undefined) {
|
||||||
|
const file = toFilePath(name);
|
||||||
|
if (value === undefined) {
|
||||||
|
await fs.promises.rm(file, { force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = JSON.stringify(value, undefined, 2);
|
||||||
|
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
||||||
|
await fs.promises.writeFile(file, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await use(storage);
|
||||||
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
|
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
|
||||||
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP), { option: true }],
|
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP), { option: true }],
|
||||||
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme), { option: true }],
|
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme), { option: true }],
|
||||||
|
|
|
||||||
21
packages/playwright-test/types/test.d.ts
vendored
21
packages/playwright-test/types/test.d.ts
vendored
|
|
@ -2668,6 +2668,23 @@ type ConnectOptions = {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Playwright Test provides a `storage` fixture for passing values between project setup and tests. TODO: examples
|
||||||
|
*/
|
||||||
|
interface Storage {
|
||||||
|
/**
|
||||||
|
* Get named item from the store.
|
||||||
|
* @param name Item name.
|
||||||
|
*/
|
||||||
|
get<T>(name: string): Promise<T | undefined>;
|
||||||
|
/**
|
||||||
|
* Set value to the store.
|
||||||
|
* @param name Item name.
|
||||||
|
* @param value Item value. The value must be serializable to JSON.
|
||||||
|
*/
|
||||||
|
set<T>(name: string, value: T | undefined): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
|
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
|
||||||
*
|
*
|
||||||
|
|
@ -3004,6 +3021,10 @@ export interface PlaywrightWorkerArgs {
|
||||||
* Learn how to [configure browser](https://playwright.dev/docs/test-configuration) and see [available options][TestOptions].
|
* Learn how to [configure browser](https://playwright.dev/docs/test-configuration) and see [available options][TestOptions].
|
||||||
*/
|
*/
|
||||||
browser: Browser;
|
browser: Browser;
|
||||||
|
/**
|
||||||
|
* [Storage] is shared between all tests in the same run.
|
||||||
|
*/
|
||||||
|
storage: Storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -600,3 +600,121 @@ test('should pass fixture defaults to tests', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should provide storage fixture', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.js': `
|
||||||
|
module.exports = {};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should store number', async ({ 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 ({ storage }) => {
|
||||||
|
expect(storage).toBeTruthy();
|
||||||
|
expect(await storage.get('object')).toBe(undefined);
|
||||||
|
await storage.set('object', { 'a': 2022 })
|
||||||
|
expect(await storage.get('object')).toEqual({ 'a': 2022 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('should share storage state between project setup and tests', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.js': `
|
||||||
|
module.exports = {
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'p1',
|
||||||
|
setup: /.*storage.setup.ts/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'storage.setup.ts': `
|
||||||
|
const { test, expect } = pwt;
|
||||||
|
test('should initialize storage', async ({ storage }) => {
|
||||||
|
expect(await storage.get('number')).toBe(undefined);
|
||||||
|
await storage.set('number', 2022)
|
||||||
|
expect(await storage.get('number')).toBe(2022);
|
||||||
|
|
||||||
|
expect(await storage.get('object')).toBe(undefined);
|
||||||
|
await storage.set('object', { 'a': 2022 })
|
||||||
|
expect(await storage.get('object')).toEqual({ 'a': 2022 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should get data from setup', async ({ storage }) => {
|
||||||
|
expect(await storage.get('number')).toBe(2022);
|
||||||
|
expect(await storage.get('object')).toEqual({ 'a': 2022 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'b.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should get data from setup', async ({ storage }) => {
|
||||||
|
expect(await storage.get('number')).toBe(2022);
|
||||||
|
expect(await storage.get('object')).toEqual({ 'a': 2022 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('should isolate storage state between projects', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.js': `
|
||||||
|
module.exports = {
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'p1',
|
||||||
|
setup: /.*storage.setup.ts/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'p2',
|
||||||
|
setup: /.*storage.setup.ts/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'storage.setup.ts': `
|
||||||
|
const { test, expect } = pwt;
|
||||||
|
test('should initialize storage', async ({ storage }, testInfo) => {
|
||||||
|
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-' + testInfo.project.name)
|
||||||
|
expect(await storage.get('name')).toBe('str-' + testInfo.project.name);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should get data from setup', async ({ storage }, testInfo) => {
|
||||||
|
expect(await storage.get('number')).toBe(2022);
|
||||||
|
expect(await storage.get('name')).toBe('str-' + testInfo.project.name);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'b.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should get data from setup', async ({ storage }, testInfo) => {
|
||||||
|
expect(await storage.get('number')).toBe(2022);
|
||||||
|
expect(await storage.get('name')).toBe('str-' + testInfo.project.name);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 2 });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
6
utils/generate_types/overrides-test.d.ts
vendored
6
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -196,6 +196,11 @@ type ConnectOptions = {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Storage {
|
||||||
|
get<T>(name: string): Promise<T | undefined>;
|
||||||
|
set<T>(name: string, value: T | undefined): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PlaywrightWorkerOptions {
|
export interface PlaywrightWorkerOptions {
|
||||||
browserName: BrowserName;
|
browserName: BrowserName;
|
||||||
defaultBrowserType: BrowserName;
|
defaultBrowserType: BrowserName;
|
||||||
|
|
@ -243,6 +248,7 @@ export interface PlaywrightTestOptions {
|
||||||
export interface PlaywrightWorkerArgs {
|
export interface PlaywrightWorkerArgs {
|
||||||
playwright: typeof import('playwright-core');
|
playwright: typeof import('playwright-core');
|
||||||
browser: Browser;
|
browser: Browser;
|
||||||
|
storage: Storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaywrightTestArgs {
|
export interface PlaywrightTestArgs {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue