chore: temporarily remove project and global setup, store (#20181)
This commit is contained in:
parent
6d63773965
commit
e08168e16e
|
|
@ -1170,24 +1170,6 @@ Test title.
|
||||||
Test function that takes one or two arguments: an object with fixtures and optional [TestInfo].
|
Test function that takes one or two arguments: an object with fixtures and optional [TestInfo].
|
||||||
|
|
||||||
|
|
||||||
## method: Test.projectSetup
|
|
||||||
* since: v1.30
|
|
||||||
|
|
||||||
Declares a project setup function. The function will be run before all other tests in the same project and if it fails the project execution will be aborted.
|
|
||||||
|
|
||||||
### param: Test.projectSetup.title
|
|
||||||
* since: v1.30
|
|
||||||
- `title` <[string]>
|
|
||||||
|
|
||||||
Project setup title.
|
|
||||||
|
|
||||||
### param: Test.projectSetup.testFunction
|
|
||||||
* since: v1.30
|
|
||||||
- `testFunction` <[function]\([Fixtures], [TestInfo]\)>
|
|
||||||
|
|
||||||
Project setup function that takes one or two arguments: an object with fixtures and optional [TestInfo].
|
|
||||||
|
|
||||||
|
|
||||||
## method: Test.setTimeout
|
## method: Test.setTimeout
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,18 +141,6 @@ export default defineConfig({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## property: TestConfig.globalScripts
|
|
||||||
* since: v1.30
|
|
||||||
- type: ?<[string]|[RegExp]|[Array]<[string]|[RegExp]>>
|
|
||||||
|
|
||||||
Files that contain global setup/teardown hooks.
|
|
||||||
|
|
||||||
**Details**
|
|
||||||
|
|
||||||
[`method: Test.beforeAll`] hooks in the matching files will run before testing starts. [`method: Test.afterAll`] hooks in the matching files will run after testing finishes.
|
|
||||||
|
|
||||||
If global setup fails, test execution will be skipped. [`method: Test.afterAll`] hooks will run in the same worker process as [`method: Test.beforeAll`].
|
|
||||||
|
|
||||||
## property: TestConfig.globalSetup
|
## property: TestConfig.globalSetup
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[string]>
|
- type: ?<[string]>
|
||||||
|
|
|
||||||
|
|
@ -201,14 +201,6 @@ Learn more about [automatic screenshots](../test-configuration.md#automatic-scre
|
||||||
## property: TestOptions.storageState = %%-js-python-context-option-storage-state-%%
|
## property: TestOptions.storageState = %%-js-python-context-option-storage-state-%%
|
||||||
* since: v1.10
|
* 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
|
## property: TestOptions.testIdAttribute
|
||||||
* since: v1.27
|
* since: v1.27
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ Filter to only run tests with a title matching one of the patterns. For example,
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[RegExp]|[Array]<[RegExp]>>
|
- 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. 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.
|
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.
|
||||||
|
|
||||||
`grepInvert` option is also useful for [tagging tests](../test-annotations.md#tag-tests).
|
`grepInvert` option is also useful for [tagging tests](../test-annotations.md#tag-tests).
|
||||||
|
|
||||||
|
|
@ -160,18 +160,6 @@ Metadata that will be put directly to the test report serialized as JSON.
|
||||||
|
|
||||||
Project name is visible in the report and during test execution.
|
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
|
## property: TestProject.snapshotDir
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[string]>
|
- type: ?<[string]>
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
||||||
|
|
@ -128,7 +128,6 @@ export class ConfigLoader {
|
||||||
this._fullConfig.shard = takeFirst(config.shard, baseFullConfig.shard);
|
this._fullConfig.shard = takeFirst(config.shard, baseFullConfig.shard);
|
||||||
this._fullConfig._ignoreSnapshots = takeFirst(config.ignoreSnapshots, baseFullConfig._ignoreSnapshots);
|
this._fullConfig._ignoreSnapshots = takeFirst(config.ignoreSnapshots, baseFullConfig._ignoreSnapshots);
|
||||||
this._fullConfig.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots);
|
this._fullConfig.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots);
|
||||||
this._fullConfig._globalScripts = takeFirst(config.globalScripts, null);
|
|
||||||
|
|
||||||
const workers = takeFirst(config.workers, '50%');
|
const workers = takeFirst(config.workers, '50%');
|
||||||
if (typeof workers === 'string') {
|
if (typeof workers === 'string') {
|
||||||
|
|
@ -152,9 +151,8 @@ export class ConfigLoader {
|
||||||
this._fullConfig._webServers = [webServers];
|
this._fullConfig._webServers = [webServers];
|
||||||
}
|
}
|
||||||
this._fullConfig.metadata = takeFirst(config.metadata, baseFullConfig.metadata);
|
this._fullConfig.metadata = takeFirst(config.metadata, baseFullConfig.metadata);
|
||||||
this._fullConfig._globalProject = this._resolveProject(config, this._fullConfig, globalScriptsProject, throwawayArtifactsPath);
|
|
||||||
this._fullConfig.projects = (config.projects || [config]).map(p => this._resolveProject(config, this._fullConfig, p, throwawayArtifactsPath));
|
this._fullConfig.projects = (config.projects || [config]).map(p => this._resolveProject(config, this._fullConfig, p, throwawayArtifactsPath));
|
||||||
this._assignUniqueProjectIds([...this._fullConfig.projects, this._fullConfig._globalProject]);
|
this._assignUniqueProjectIds(this._fullConfig.projects);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
||||||
|
|
@ -217,7 +215,6 @@ export class ConfigLoader {
|
||||||
const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results'));
|
const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results'));
|
||||||
const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir);
|
const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir);
|
||||||
const name = takeFirst(projectConfig.name, config.name, '');
|
const name = takeFirst(projectConfig.name, config.name, '');
|
||||||
const _setupMatch = takeFirst(projectConfig.setupMatch, []);
|
|
||||||
|
|
||||||
const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
|
const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
|
||||||
const snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
|
const snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
|
||||||
|
|
@ -234,7 +231,6 @@ export class ConfigLoader {
|
||||||
metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
|
metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
|
||||||
name,
|
name,
|
||||||
testDir,
|
testDir,
|
||||||
_setupMatch,
|
|
||||||
_respectGitIgnore: respectGitIgnore,
|
_respectGitIgnore: respectGitIgnore,
|
||||||
snapshotDir,
|
snapshotDir,
|
||||||
snapshotPathTemplate,
|
snapshotPathTemplate,
|
||||||
|
|
@ -425,7 +421,7 @@ function validateProject(file: string, project: Project, title: string) {
|
||||||
throw errorWithFile(file, `${title}.testDir must be a string`);
|
throw errorWithFile(file, `${title}.testDir must be a string`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const prop of ['testIgnore', 'testMatch', 'setupMatch'] as const) {
|
for (const prop of ['testIgnore', 'testMatch'] as const) {
|
||||||
if (prop in project && project[prop] !== undefined) {
|
if (prop in project && project[prop] !== undefined) {
|
||||||
const value = project[prop];
|
const value = project[prop];
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
|
|
@ -450,11 +446,6 @@ function validateProject(file: string, project: Project, title: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const globalScriptsProject: Project = {
|
|
||||||
name: 'Global Scripts',
|
|
||||||
repeatEach: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const baseFullConfig: FullConfigInternal = {
|
export const baseFullConfig: FullConfigInternal = {
|
||||||
forbidOnly: false,
|
forbidOnly: false,
|
||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
|
|
@ -483,8 +474,6 @@ export const baseFullConfig: FullConfigInternal = {
|
||||||
_storeDir: '',
|
_storeDir: '',
|
||||||
_maxConcurrentTestGroups: 0,
|
_maxConcurrentTestGroups: 0,
|
||||||
_ignoreSnapshots: false,
|
_ignoreSnapshots: false,
|
||||||
_globalScripts: null,
|
|
||||||
_globalProject: { } as FullProjectInternal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[]|undefined {
|
function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[]|undefined {
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestBeginPayload, TestEndPayload, DonePayload, TestOutputPayload, StepBeginPayload, StepEndPayload, TeardownErrorsPayload, WatchTestResolvedPayload, RunPayload, SerializedLoaderData } from './ipc';
|
import type { TestBeginPayload, TestEndPayload, DonePayload, TestOutputPayload, StepBeginPayload, StepEndPayload, TeardownErrorsPayload, RunPayload, SerializedLoaderData } from './ipc';
|
||||||
import type { TestResult, Reporter, TestStep, TestError } from '../types/testReporter';
|
import type { TestResult, Reporter, TestStep, TestError } from '../types/testReporter';
|
||||||
import type { Suite } from './test';
|
import type { Suite } from './test';
|
||||||
import type { ConfigLoader } from './configLoader';
|
import type { ConfigLoader } from './configLoader';
|
||||||
import type { ProcessExitData } from './processHost';
|
import type { ProcessExitData } from './processHost';
|
||||||
import { TestCase } from './test';
|
import type { TestCase } from './test';
|
||||||
import { ManualPromise } from 'playwright-core/lib/utils';
|
import { ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import { TestTypeImpl } from './testType';
|
|
||||||
import { WorkerHost } from './workerHost';
|
import { WorkerHost } from './workerHost';
|
||||||
|
|
||||||
export type TestGroup = {
|
export type TestGroup = {
|
||||||
|
|
@ -30,8 +29,6 @@ export type TestGroup = {
|
||||||
repeatEachIndex: number;
|
repeatEachIndex: number;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
tests: TestCase[];
|
tests: TestCase[];
|
||||||
watchMode: boolean;
|
|
||||||
phase: 'test' | 'projectSetup' | 'globalSetup';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type TestResultData = {
|
type TestResultData = {
|
||||||
|
|
@ -169,15 +166,12 @@ export class Dispatcher {
|
||||||
entries: testGroup.tests.map(test => {
|
entries: testGroup.tests.map(test => {
|
||||||
return { testId: test.id, retry: test.results.length };
|
return { testId: test.id, retry: test.results.length };
|
||||||
}),
|
}),
|
||||||
watchMode: testGroup.watchMode,
|
|
||||||
phase: testGroup.phase,
|
|
||||||
};
|
};
|
||||||
worker.runTestGroup(runPayload);
|
worker.runTestGroup(runPayload);
|
||||||
|
|
||||||
let doneCallback = () => {};
|
let doneCallback = () => {};
|
||||||
const result = new Promise<void>(f => doneCallback = f);
|
const result = new Promise<void>(f => doneCallback = f);
|
||||||
const doneWithJob = () => {
|
const doneWithJob = () => {
|
||||||
worker.removeListener('watchTestResolved', onWatchTestResolved);
|
|
||||||
worker.removeListener('testBegin', onTestBegin);
|
worker.removeListener('testBegin', onTestBegin);
|
||||||
worker.removeListener('testEnd', onTestEnd);
|
worker.removeListener('testEnd', onTestEnd);
|
||||||
worker.removeListener('stepBegin', onStepBegin);
|
worker.removeListener('stepBegin', onStepBegin);
|
||||||
|
|
@ -190,12 +184,6 @@ export class Dispatcher {
|
||||||
const remainingByTestId = new Map(testGroup.tests.map(e => [e.id, e]));
|
const remainingByTestId = new Map(testGroup.tests.map(e => [e.id, e]));
|
||||||
const failedTestIds = new Set<string>();
|
const failedTestIds = new Set<string>();
|
||||||
|
|
||||||
const onWatchTestResolved = (params: WatchTestResolvedPayload) => {
|
|
||||||
const test = new TestCase(params.title, () => {}, new TestTypeImpl([]), params.location);
|
|
||||||
this._testById.set(params.testId, { test, resultByWorkerIndex: new Map() });
|
|
||||||
};
|
|
||||||
worker.addListener('watchTestResolved', onWatchTestResolved);
|
|
||||||
|
|
||||||
const onTestBegin = (params: TestBeginPayload) => {
|
const onTestBegin = (params: TestBeginPayload) => {
|
||||||
const data = this._testById.get(params.testId)!;
|
const data = this._testById.get(params.testId)!;
|
||||||
const result = data.test._appendTestResult();
|
const result = data.test._appendTestResult();
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,12 @@ import type { APIRequestContext, BrowserContext, BrowserContextOptions, LaunchOp
|
||||||
import * as playwrightLibrary from 'playwright-core';
|
import * as playwrightLibrary from 'playwright-core';
|
||||||
import { createGuid, debugMode, removeFolders, addStackIgnoreFilter } from 'playwright-core/lib/utils';
|
import { createGuid, debugMode, removeFolders, addStackIgnoreFilter } from 'playwright-core/lib/utils';
|
||||||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test';
|
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test';
|
||||||
import { store as _baseStore } from './store';
|
|
||||||
import type { TestInfoImpl } from './testInfo';
|
import type { TestInfoImpl } from './testInfo';
|
||||||
import { rootTestType } from './testType';
|
import { rootTestType } from './testType';
|
||||||
import { type ContextReuseMode } from './types';
|
import { type ContextReuseMode } from './types';
|
||||||
export { expect } from './expect';
|
export { expect } from './expect';
|
||||||
export { addRunnerPlugin as _addRunnerPlugin } from './plugins';
|
export { addRunnerPlugin as _addRunnerPlugin } from './plugins';
|
||||||
export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
||||||
export const store = _baseStore;
|
|
||||||
|
|
||||||
addStackIgnoreFilter((frame: StackFrame) => frame.file.startsWith(path.dirname(require.resolve('../package.json'))));
|
addStackIgnoreFilter((frame: StackFrame) => frame.file.startsWith(path.dirname(require.resolve('../package.json'))));
|
||||||
|
|
||||||
|
|
@ -145,7 +143,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
||||||
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
||||||
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
||||||
storageStateName: [undefined, { option: true }],
|
|
||||||
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
||||||
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { 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 }],
|
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === undefined ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
|
||||||
|
|
@ -175,7 +172,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
permissions,
|
permissions,
|
||||||
proxy,
|
proxy,
|
||||||
storageState,
|
storageState,
|
||||||
storageStateName,
|
|
||||||
viewport,
|
viewport,
|
||||||
timezoneId,
|
timezoneId,
|
||||||
userAgent,
|
userAgent,
|
||||||
|
|
@ -214,14 +210,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
options.permissions = permissions;
|
options.permissions = permissions;
|
||||||
if (proxy !== undefined)
|
if (proxy !== undefined)
|
||||||
options.proxy = proxy;
|
options.proxy = proxy;
|
||||||
if (storageStateName !== undefined) {
|
if (storageState !== undefined)
|
||||||
const value = await store.get(storageStateName);
|
|
||||||
if (!value)
|
|
||||||
throw new Error(`Cannot find value in the store for storageStateName: "${storageStateName}"`);
|
|
||||||
options.storageState = value as any;
|
|
||||||
} else if (storageState !== undefined) {
|
|
||||||
options.storageState = storageState;
|
options.storageState = storageState;
|
||||||
}
|
|
||||||
if (timezoneId !== undefined)
|
if (timezoneId !== undefined)
|
||||||
options.timezoneId = timezoneId;
|
options.timezoneId = timezoneId;
|
||||||
if (userAgent !== undefined)
|
if (userAgent !== undefined)
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,6 @@ export type WorkerInitParams = {
|
||||||
loader: SerializedLoaderData;
|
loader: SerializedLoaderData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WatchTestResolvedPayload = {
|
|
||||||
testId: string;
|
|
||||||
title: string;
|
|
||||||
location: { file: string, line: number, column: number };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TestBeginPayload = {
|
export type TestBeginPayload = {
|
||||||
testId: string;
|
testId: string;
|
||||||
startWallTime: number; // milliseconds since unix epoch
|
startWallTime: number; // milliseconds since unix epoch
|
||||||
|
|
@ -92,8 +86,6 @@ export type TestEntry = {
|
||||||
export type RunPayload = {
|
export type RunPayload = {
|
||||||
file: string;
|
file: string;
|
||||||
entries: TestEntry[];
|
entries: TestEntry[];
|
||||||
watchMode: boolean;
|
|
||||||
phase: 'test' | 'projectSetup' | 'globalSetup';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DonePayload = {
|
export type DonePayload = {
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ export class Runner {
|
||||||
|
|
||||||
async listTestFiles(projectNames: string[] | undefined): Promise<any> {
|
async listTestFiles(projectNames: string[] | undefined): Promise<any> {
|
||||||
const projects = this._collectProjects(projectNames);
|
const projects = this._collectProjects(projectNames);
|
||||||
const { filesByProject } = await this._collectFiles(projects, []);
|
const filesByProject = await this._collectFiles(projects, []);
|
||||||
const report: any = {
|
const report: any = {
|
||||||
projects: []
|
projects: []
|
||||||
};
|
};
|
||||||
|
|
@ -239,98 +239,45 @@ export class Runner {
|
||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _collectFiles(projects: FullProjectInternal[], commandLineFileFilters: TestFileFilter[]): Promise<{filesByProject: Map<FullProjectInternal, string[]>; setupFiles: Set<string>, globalSetupFiles: Set<string>}> {
|
private async _collectFiles(projects: FullProjectInternal[], commandLineFileFilters: TestFileFilter[]): Promise<Map<FullProjectInternal, string[]>> {
|
||||||
const extensions = ['.js', '.ts', '.mjs', '.tsx', '.jsx'];
|
const extensions = ['.js', '.ts', '.mjs', '.tsx', '.jsx'];
|
||||||
const testFileExtension = (file: string) => extensions.includes(path.extname(file));
|
const testFileExtension = (file: string) => extensions.includes(path.extname(file));
|
||||||
const filesByProject = new Map<FullProjectInternal, string[]>();
|
const filesByProject = new Map<FullProjectInternal, string[]>();
|
||||||
const setupFiles = new Set<string>();
|
|
||||||
const fileToProjectName = new Map<string, string>();
|
const fileToProjectName = new Map<string, string>();
|
||||||
const commandLineFileMatcher = commandLineFileFilters.length ? createFileMatcherFromFilters(commandLineFileFilters) : () => true;
|
const commandLineFileMatcher = commandLineFileFilters.length ? createFileMatcherFromFilters(commandLineFileFilters) : () => true;
|
||||||
|
|
||||||
const config = this._configLoader.fullConfig();
|
|
||||||
const globalSetupFiles = new Set<string>();
|
|
||||||
if (config._globalScripts) {
|
|
||||||
const allFiles = await collectFiles(config.rootDir, true);
|
|
||||||
const globalScriptMatch = createFileMatcher(config._globalScripts);
|
|
||||||
const globalScripts = allFiles.filter(file => {
|
|
||||||
if (!testFileExtension(file) || !globalScriptMatch(file))
|
|
||||||
return false;
|
|
||||||
fileToProjectName.set(file, config._globalProject.name);
|
|
||||||
globalSetupFiles.add(file);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
filesByProject.set(config._globalProject, globalScripts);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
const allFiles = await collectFiles(project.testDir, project._respectGitIgnore);
|
const allFiles = await collectFiles(project.testDir, project._respectGitIgnore);
|
||||||
const setupMatch = createFileMatcher(project._setupMatch);
|
|
||||||
const testMatch = createFileMatcher(project.testMatch);
|
const testMatch = createFileMatcher(project.testMatch);
|
||||||
const testIgnore = createFileMatcher(project.testIgnore);
|
const testIgnore = createFileMatcher(project.testIgnore);
|
||||||
const testFiles = allFiles.filter(file => {
|
const testFiles = allFiles.filter(file => {
|
||||||
if (!testFileExtension(file))
|
if (!testFileExtension(file))
|
||||||
return false;
|
return false;
|
||||||
const isSetup = setupMatch(file);
|
|
||||||
const isTest = !testIgnore(file) && testMatch(file) && commandLineFileMatcher(file);
|
const isTest = !testIgnore(file) && testMatch(file) && commandLineFileMatcher(file);
|
||||||
if (!isTest && !isSetup)
|
if (!isTest)
|
||||||
return false;
|
return false;
|
||||||
if (isSetup && isTest)
|
|
||||||
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)}"`);
|
|
||||||
} 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}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileToProjectName.set(file, project.name);
|
fileToProjectName.set(file, project.name);
|
||||||
if (isSetup)
|
|
||||||
setupFiles.add(file);
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
filesByProject.set(project, testFiles);
|
filesByProject.set(project, testFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { filesByProject, setupFiles, globalSetupFiles };
|
return filesByProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _collectTestGroups(options: RunOptions): Promise<{ rootSuite: Suite, globalSetupGroups: TestGroup[], projectSetupGroups: TestGroup[], testGroups: TestGroup[] }> {
|
private async _collectTestGroups(options: RunOptions): Promise<{ rootSuite: Suite, testGroups: TestGroup[] }> {
|
||||||
const config = this._configLoader.fullConfig();
|
const config = this._configLoader.fullConfig();
|
||||||
const projects = this._collectProjects(options.projectFilter);
|
const projects = this._collectProjects(options.projectFilter);
|
||||||
const { filesByProject, setupFiles, globalSetupFiles } = await this._collectFiles(projects, options.testFileFilters);
|
const filesByProject = await this._collectFiles(projects, options.testFileFilters);
|
||||||
|
const result = await this._createFilteredRootSuite(options, filesByProject);
|
||||||
let result = await this._createFilteredRootSuite(options, filesByProject, new Set(), !!setupFiles.size, setupFiles, globalSetupFiles);
|
|
||||||
if (setupFiles.size) {
|
|
||||||
const allTests = result.rootSuite.allTests();
|
|
||||||
const tests = allTests.filter(test => test._phase === 'test');
|
|
||||||
// If >0 tests match and
|
|
||||||
// - none of the setup files match the filter then we run all setup files,
|
|
||||||
// - 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, setupFiles, globalSetupFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._fatalErrors.push(...result.fatalErrors);
|
this._fatalErrors.push(...result.fatalErrors);
|
||||||
const { rootSuite } = result;
|
const { rootSuite } = result;
|
||||||
|
|
||||||
const allTestGroups = createTestGroups(rootSuite.suites, config.workers);
|
const testGroups = createTestGroups(rootSuite.suites, config.workers);
|
||||||
const globalSetupGroups = [];
|
return { rootSuite, testGroups };
|
||||||
const projectSetupGroups = [];
|
|
||||||
const testGroups = [];
|
|
||||||
for (const group of allTestGroups) {
|
|
||||||
if (group.phase === 'projectSetup')
|
|
||||||
projectSetupGroups.push(group);
|
|
||||||
else if (group.phase === 'globalSetup')
|
|
||||||
globalSetupGroups.push(group);
|
|
||||||
else
|
|
||||||
testGroups.push(group);
|
|
||||||
}
|
|
||||||
return { rootSuite, globalSetupGroups, projectSetupGroups, testGroups };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createFilteredRootSuite(options: RunOptions, filesByProject: Map<FullProjectInternal, string[]>, doNotFilterFiles: Set<string>, shouldCloneTests: boolean, setupFiles: Set<string>, globalSetupFiles: Set<string>): Promise<{rootSuite: Suite, fatalErrors: TestError[]}> {
|
private async _createFilteredRootSuite(options: RunOptions, filesByProject: Map<FullProjectInternal, string[]>): Promise<{rootSuite: Suite, fatalErrors: TestError[]}> {
|
||||||
const config = this._configLoader.fullConfig();
|
const config = this._configLoader.fullConfig();
|
||||||
const fatalErrors: TestError[] = [];
|
const fatalErrors: TestError[] = [];
|
||||||
const allTestFiles = new Set<string>();
|
const allTestFiles = new Set<string>();
|
||||||
|
|
@ -341,23 +288,18 @@ export class Runner {
|
||||||
// Add all tests.
|
// Add all tests.
|
||||||
const preprocessRoot = new Suite('', 'root');
|
const preprocessRoot = new Suite('', 'root');
|
||||||
for (const file of allTestFiles) {
|
for (const file of allTestFiles) {
|
||||||
let type: 'test' | 'projectSetup' | 'globalSetup' = 'test';
|
const fileSuite = await testLoader.loadTestFile(file, 'runner');
|
||||||
if (globalSetupFiles.has(file))
|
|
||||||
type = 'globalSetup';
|
|
||||||
else if (setupFiles.has(file))
|
|
||||||
type = 'projectSetup';
|
|
||||||
const fileSuite = await testLoader.loadTestFile(file, 'runner', type);
|
|
||||||
if (fileSuite._loadError)
|
if (fileSuite._loadError)
|
||||||
fatalErrors.push(fileSuite._loadError);
|
fatalErrors.push(fileSuite._loadError);
|
||||||
// We have to clone only if there maybe subsequent calls of this method.
|
// We have to clone only if there maybe subsequent calls of this method.
|
||||||
preprocessRoot._addSuite(shouldCloneTests ? fileSuite._deepClone() : fileSuite);
|
preprocessRoot._addSuite(fileSuite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complain about duplicate titles.
|
// Complain about duplicate titles.
|
||||||
fatalErrors.push(...createDuplicateTitlesErrors(config, preprocessRoot));
|
fatalErrors.push(...createDuplicateTitlesErrors(config, preprocessRoot));
|
||||||
|
|
||||||
// Filter tests to respect line/column filter.
|
// Filter tests to respect line/column filter.
|
||||||
filterByFocusedLine(preprocessRoot, options.testFileFilters, doNotFilterFiles);
|
filterByFocusedLine(preprocessRoot, options.testFileFilters);
|
||||||
|
|
||||||
// Complain about only.
|
// Complain about only.
|
||||||
if (config.forbidOnly) {
|
if (config.forbidOnly) {
|
||||||
|
|
@ -368,7 +310,7 @@ export class Runner {
|
||||||
|
|
||||||
// Filter only.
|
// Filter only.
|
||||||
if (!options.listOnly)
|
if (!options.listOnly)
|
||||||
filterOnly(preprocessRoot, doNotFilterFiles);
|
filterOnly(preprocessRoot);
|
||||||
|
|
||||||
// Generate projects.
|
// Generate projects.
|
||||||
const fileSuites = new Map<string, Suite>();
|
const fileSuites = new Map<string, Suite>();
|
||||||
|
|
@ -381,8 +323,6 @@ export class Runner {
|
||||||
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
||||||
|
|
||||||
const titleMatcher = (test: TestCase) => {
|
const titleMatcher = (test: TestCase) => {
|
||||||
if (doNotFilterFiles.has(test._requireFile))
|
|
||||||
return true;
|
|
||||||
const grepTitle = test.titlePath().join(' ');
|
const grepTitle = test.titlePath().join(' ');
|
||||||
if (grepInvertMatcher?.(grepTitle))
|
if (grepInvertMatcher?.(grepTitle))
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -408,7 +348,7 @@ export class Runner {
|
||||||
return { rootSuite, fatalErrors };
|
return { rootSuite, fatalErrors };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterForCurrentShard(rootSuite: Suite, projectSetupGroups: TestGroup[], testGroups: TestGroup[]) {
|
private _filterForCurrentShard(rootSuite: Suite, testGroups: TestGroup[]) {
|
||||||
const shard = this._configLoader.fullConfig().shard;
|
const shard = this._configLoader.fullConfig().shard;
|
||||||
if (!shard)
|
if (!shard)
|
||||||
return;
|
return;
|
||||||
|
|
@ -447,17 +387,6 @@ export class Runner {
|
||||||
testGroups.length = 0;
|
testGroups.length = 0;
|
||||||
testGroups.push(...shardTestGroups);
|
testGroups.push(...shardTestGroups);
|
||||||
|
|
||||||
const shardSetupGroups = [];
|
|
||||||
for (const group of projectSetupGroups) {
|
|
||||||
if (!shardProjects.has(group.projectId))
|
|
||||||
continue;
|
|
||||||
shardSetupGroups.push(group);
|
|
||||||
for (const test of group.tests)
|
|
||||||
shardTests.add(test);
|
|
||||||
}
|
|
||||||
projectSetupGroups.length = 0;
|
|
||||||
projectSetupGroups.push(...shardSetupGroups);
|
|
||||||
|
|
||||||
if (!shardTests.size) {
|
if (!shardTests.size) {
|
||||||
// Filtering with "only semantics" does not work when we have zero tests - it leaves all the tests.
|
// Filtering with "only semantics" does not work when we have zero tests - it leaves all the tests.
|
||||||
// We need an empty suite in this case.
|
// We need an empty suite in this case.
|
||||||
|
|
@ -465,10 +394,7 @@ export class Runner {
|
||||||
rootSuite.suites = [];
|
rootSuite.suites = [];
|
||||||
rootSuite.tests = [];
|
rootSuite.tests = [];
|
||||||
} else {
|
} else {
|
||||||
// Unlike project setup files global setup always run regardless of the selected tests.
|
filterSuiteWithOnlySemantics(rootSuite, () => false, test => shardTests.has(test));
|
||||||
// Because of that we don't add global setup entries to shardTests to avoid running empty
|
|
||||||
// shards which have only global setup.
|
|
||||||
filterSuiteWithOnlySemantics(rootSuite, () => false, test => shardTests.has(test) || test._phase === 'globalSetup');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,15 +402,15 @@ export class Runner {
|
||||||
const config = this._configLoader.fullConfig();
|
const config = this._configLoader.fullConfig();
|
||||||
// Each entry is an array of test groups that can be run concurrently. All
|
// Each entry is an array of test groups that can be run concurrently. All
|
||||||
// test groups from the previos entries must finish before entry starts.
|
// test groups from the previos entries must finish before entry starts.
|
||||||
const { rootSuite, globalSetupGroups, projectSetupGroups, testGroups } = await this._collectTestGroups(options);
|
const { rootSuite, testGroups } = await this._collectTestGroups(options);
|
||||||
|
|
||||||
// Fail when no tests.
|
// Fail when no tests.
|
||||||
if (!rootSuite.allTests().length && !options.passWithNoTests)
|
if (!rootSuite.allTests().length && !options.passWithNoTests)
|
||||||
this._fatalErrors.push(createNoTestsError());
|
this._fatalErrors.push(createNoTestsError());
|
||||||
|
|
||||||
this._filterForCurrentShard(rootSuite, projectSetupGroups, testGroups);
|
this._filterForCurrentShard(rootSuite, testGroups);
|
||||||
|
|
||||||
config._maxConcurrentTestGroups = Math.max(globalSetupGroups.length, projectSetupGroups.length, testGroups.length);
|
config._maxConcurrentTestGroups = testGroups.length;
|
||||||
|
|
||||||
// Report begin
|
// Report begin
|
||||||
this._reporter.onBegin?.(config, rootSuite);
|
this._reporter.onBegin?.(config, rootSuite);
|
||||||
|
|
@ -521,24 +447,7 @@ export class Runner {
|
||||||
|
|
||||||
// Run tests.
|
// Run tests.
|
||||||
try {
|
try {
|
||||||
// TODO: run only setups, keep workers alive, inherit process.env from global setup workers
|
const dispatchResult = await this._dispatchToWorkers(testGroups);
|
||||||
let dispatchResult = await this._dispatchToWorkers(globalSetupGroups);
|
|
||||||
if (dispatchResult === 'success') {
|
|
||||||
if (globalSetupGroups.some(group => group.tests.some(test => !test.ok()))) {
|
|
||||||
this._skipTestsFromMatchingGroups([...testGroups, ...projectSetupGroups], () => true);
|
|
||||||
} else {
|
|
||||||
dispatchResult = await this._dispatchToWorkers(projectSetupGroups);
|
|
||||||
if (dispatchResult === 'success') {
|
|
||||||
const failedSetupProjectIds = new Set<string>();
|
|
||||||
for (const testGroup of projectSetupGroups) {
|
|
||||||
if (testGroup.tests.some(test => !test.ok()))
|
|
||||||
failedSetupProjectIds.add(testGroup.projectId);
|
|
||||||
}
|
|
||||||
const testGroupsToRun = this._skipTestsFromMatchingGroups(testGroups, group => failedSetupProjectIds.has(group.projectId));
|
|
||||||
dispatchResult = await this._dispatchToWorkers(testGroupsToRun);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dispatchResult === 'signal') {
|
if (dispatchResult === 'signal') {
|
||||||
result.status = 'interrupted';
|
result.status = 'interrupted';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -694,11 +603,11 @@ export class Runner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterOnly(suite: Suite, doNotFilterFiles: Set<string>) {
|
function filterOnly(suite: Suite) {
|
||||||
if (!suite._getOnlyItems().length)
|
if (!suite._getOnlyItems().length)
|
||||||
return;
|
return;
|
||||||
const suiteFilter = (suite: Suite) => suite._only || doNotFilterFiles.has(suite._requireFile);
|
const suiteFilter = (suite: Suite) => suite._only;
|
||||||
const testFilter = (test: TestCase) => test._only || doNotFilterFiles.has(test._requireFile);
|
const testFilter = (test: TestCase) => test._only;
|
||||||
return filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter);
|
return filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -708,13 +617,13 @@ function createFileMatcherFromFilter(filter: TestFileFilter) {
|
||||||
fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
|
fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByFocusedLine(suite: Suite, focusedTestFileLines: TestFileFilter[], doNotFilterFiles: Set<string>) {
|
function filterByFocusedLine(suite: Suite, focusedTestFileLines: TestFileFilter[]) {
|
||||||
if (!focusedTestFileLines.length)
|
if (!focusedTestFileLines.length)
|
||||||
return;
|
return;
|
||||||
const matchers = focusedTestFileLines.map(createFileMatcherFromFilter);
|
const matchers = focusedTestFileLines.map(createFileMatcherFromFilter);
|
||||||
const testFileLineMatches = (testFileName: string, testLine: number, testColumn: number) => matchers.some(m => m(testFileName, testLine, testColumn));
|
const testFileLineMatches = (testFileName: string, testLine: number, testColumn: number) => matchers.some(m => m(testFileName, testLine, testColumn));
|
||||||
const suiteFilter = (suite: Suite) => doNotFilterFiles.has(suite._requireFile) || !!suite.location && testFileLineMatches(suite.location.file, suite.location.line, suite.location.column);
|
const suiteFilter = (suite: Suite) => !!suite.location && testFileLineMatches(suite.location.file, suite.location.line, suite.location.column);
|
||||||
const testFilter = (test: TestCase) => doNotFilterFiles.has(test._requireFile) || testFileLineMatches(test.location.file, test.location.line, test.location.column);
|
const testFilter = (test: TestCase) => testFileLineMatches(test.location.file, test.location.line, test.location.column);
|
||||||
return filterSuite(suite, suiteFilter, testFilter);
|
return filterSuite(suite, suiteFilter, testFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -859,8 +768,6 @@ function createTestGroups(projectSuites: Suite[], workers: number): TestGroup[]
|
||||||
repeatEachIndex: test.repeatEachIndex,
|
repeatEachIndex: test.repeatEachIndex,
|
||||||
projectId: test._projectId,
|
projectId: test._projectId,
|
||||||
tests: [],
|
tests: [],
|
||||||
watchMode: false,
|
|
||||||
phase: test._phase,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright Microsoft Corporation. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 implements TestStore {
|
|
||||||
private _toFilePath(name: string) {
|
|
||||||
const testInfo = currentTestInfo();
|
|
||||||
if (!testInfo)
|
|
||||||
throw new Error('store can only be called while test is running');
|
|
||||||
const fileName = sanitizeForFilePath(trimLongString(name)) + '.json';
|
|
||||||
return path.join(testInfo.config._storeDir, testInfo.project._id, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async get<T>(name: string) {
|
|
||||||
const file = this._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 = this._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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const store = new JsonStore();
|
|
||||||
|
|
@ -23,7 +23,6 @@ class Base {
|
||||||
title: string;
|
title: string;
|
||||||
_only = false;
|
_only = false;
|
||||||
_requireFile: string = '';
|
_requireFile: string = '';
|
||||||
_phase: 'test' | 'projectSetup' | 'globalSetup' = 'test';
|
|
||||||
|
|
||||||
constructor(title: string) {
|
constructor(title: string) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
|
@ -121,7 +120,6 @@ export class Suite extends Base implements reporterTypes.Suite {
|
||||||
suite._only = this._only;
|
suite._only = this._only;
|
||||||
suite.location = this.location;
|
suite.location = this.location;
|
||||||
suite._requireFile = this._requireFile;
|
suite._requireFile = this._requireFile;
|
||||||
suite._phase = this._phase;
|
|
||||||
suite._use = this._use.slice();
|
suite._use = this._use.slice();
|
||||||
suite._hooks = this._hooks.slice();
|
suite._hooks = this._hooks.slice();
|
||||||
suite._timeout = this._timeout;
|
suite._timeout = this._timeout;
|
||||||
|
|
@ -193,7 +191,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
const test = new TestCase(this.title, this.fn, this._testType, this.location);
|
const test = new TestCase(this.title, this.fn, this._testType, this.location);
|
||||||
test._only = this._only;
|
test._only = this._only;
|
||||||
test._requireFile = this._requireFile;
|
test._requireFile = this._requireFile;
|
||||||
test._phase = this._phase;
|
|
||||||
test.expectedStatus = this.expectedStatus;
|
test.expectedStatus = this.expectedStatus;
|
||||||
test.annotations = this.annotations.slice();
|
test.annotations = this.annotations.slice();
|
||||||
test._annotateWithInheritence = this._annotateWithInheritence;
|
test._annotateWithInheritence = this._annotateWithInheritence;
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,11 @@ export class TestLoader {
|
||||||
this._fullConfig = fullConfig;
|
this._fullConfig = fullConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTestFile(file: string, environment: 'runner' | 'worker', phase: 'test' | 'projectSetup' | 'globalSetup') {
|
async loadTestFile(file: string, environment: 'runner' | 'worker') {
|
||||||
if (cachedFileSuites.has(file))
|
if (cachedFileSuites.has(file))
|
||||||
return cachedFileSuites.get(file)!;
|
return cachedFileSuites.get(file)!;
|
||||||
const suite = new Suite(path.relative(this._fullConfig.rootDir, file) || path.basename(file), 'file');
|
const suite = new Suite(path.relative(this._fullConfig.rootDir, file) || path.basename(file), 'file');
|
||||||
suite._requireFile = file;
|
suite._requireFile = file;
|
||||||
suite._phase = phase;
|
|
||||||
suite.location = { file, line: 0, column: 0 };
|
suite.location = { file, line: 0, column: 0 };
|
||||||
|
|
||||||
setCurrentlyLoadingFileSuite(suite);
|
setCurrentlyLoadingFileSuite(suite);
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,6 @@ export class TestTypeImpl {
|
||||||
test.step = wrapFunctionWithLocation(this._step.bind(this));
|
test.step = wrapFunctionWithLocation(this._step.bind(this));
|
||||||
test.use = wrapFunctionWithLocation(this._use.bind(this));
|
test.use = wrapFunctionWithLocation(this._use.bind(this));
|
||||||
test.extend = wrapFunctionWithLocation(this._extend.bind(this));
|
test.extend = wrapFunctionWithLocation(this._extend.bind(this));
|
||||||
test.projectSetup = wrapFunctionWithLocation(this._createTest.bind(this, 'projectSetup'));
|
|
||||||
(test.projectSetup as any).only = wrapFunctionWithLocation(this._createTest.bind(this, 'projectSetupOnly'));
|
|
||||||
test._extendTest = wrapFunctionWithLocation(this._extendTest.bind(this));
|
test._extendTest = wrapFunctionWithLocation(this._extendTest.bind(this));
|
||||||
test.info = () => {
|
test.info = () => {
|
||||||
const result = currentTestInfo();
|
const result = currentTestInfo();
|
||||||
|
|
@ -67,7 +65,7 @@ export class TestTypeImpl {
|
||||||
this.test = test;
|
this.test = test;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _currentSuite(location: Location, title: string, allowedContext: 'test' | 'projectSetup' | 'any'): Suite | undefined {
|
private _currentSuite(location: Location, title: string): Suite | undefined {
|
||||||
const suite = currentlyLoadingFileSuite();
|
const suite = currentlyLoadingFileSuite();
|
||||||
if (!suite) {
|
if (!suite) {
|
||||||
addFatalError([
|
addFatalError([
|
||||||
|
|
@ -80,36 +78,19 @@ export class TestTypeImpl {
|
||||||
].join('\n'), location);
|
].join('\n'), location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (allowedContext === 'projectSetup' && suite._phase !== 'projectSetup')
|
|
||||||
addFatalError(`${title} is only allowed in a project setup file.`, location);
|
|
||||||
else if (allowedContext === 'test' && suite._phase !== 'test' && suite._phase !== 'globalSetup')
|
|
||||||
addFatalError(`${title} is not allowed in a setup file.`, location);
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'projectSetup' | 'projectSetupOnly', location: Location, title: string, fn: Function) {
|
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme', location: Location, title: string, fn: Function) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
let functionTitle = 'test()';
|
const suite = this._currentSuite(location, 'test()');
|
||||||
let allowedContext: 'test' | 'projectSetup' | 'any' = 'any';
|
|
||||||
switch (type) {
|
|
||||||
case 'projectSetup':
|
|
||||||
case 'projectSetupOnly':
|
|
||||||
functionTitle = 'test.projectSetup()';
|
|
||||||
allowedContext = 'projectSetup';
|
|
||||||
break;
|
|
||||||
case 'default':
|
|
||||||
allowedContext = 'test';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const suite = this._currentSuite(location, functionTitle, allowedContext);
|
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
const test = new TestCase(title, fn, this, location);
|
const test = new TestCase(title, fn, this, location);
|
||||||
test._requireFile = suite._requireFile;
|
test._requireFile = suite._requireFile;
|
||||||
test._phase = suite._phase;
|
|
||||||
suite._addTest(test);
|
suite._addTest(test);
|
||||||
|
|
||||||
if (type === 'only' || type === 'projectSetupOnly')
|
if (type === 'only')
|
||||||
test._only = true;
|
test._only = true;
|
||||||
if (type === 'skip' || type === 'fixme') {
|
if (type === 'skip' || type === 'fixme') {
|
||||||
test.annotations.push({ type });
|
test.annotations.push({ type });
|
||||||
|
|
@ -123,7 +104,7 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, title: string | Function, fn?: Function) {
|
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, title: string | Function, fn?: Function) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, 'test.describe()', 'any');
|
const suite = this._currentSuite(location, 'test.describe()');
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -134,7 +115,6 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
const child = new Suite(title, 'describe');
|
const child = new Suite(title, 'describe');
|
||||||
child._requireFile = suite._requireFile;
|
child._requireFile = suite._requireFile;
|
||||||
child._phase = suite._phase;
|
|
||||||
child.location = location;
|
child.location = location;
|
||||||
suite._addSuite(child);
|
suite._addSuite(child);
|
||||||
|
|
||||||
|
|
@ -160,7 +140,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, fn: Function) {
|
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, fn: Function) {
|
||||||
const suite = this._currentSuite(location, `test.${name}()`, 'test');
|
const suite = this._currentSuite(location, `test.${name}()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
suite._hooks.push({ type: name, fn, location });
|
suite._hooks.push({ type: name, fn, location });
|
||||||
|
|
@ -168,7 +148,7 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, `test.describe.configure()`, 'any');
|
const suite = this._currentSuite(location, `test.describe.configure()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -235,7 +215,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _use(location: Location, fixtures: Fixtures) {
|
private _use(location: Location, fixtures: Fixtures) {
|
||||||
const suite = this._currentSuite(location, `test.use()`, 'any');
|
const suite = this._currentSuite(location, `test.use()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
suite._use.push({ fixtures, location });
|
suite._use.push({ fixtures, location });
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,6 @@ export interface FullConfigInternal extends FullConfigPublic {
|
||||||
*/
|
*/
|
||||||
webServer: FullConfigPublic['webServer'];
|
webServer: FullConfigPublic['webServer'];
|
||||||
_webServers: Exclude<FullConfigPublic['webServer'], null>[];
|
_webServers: Exclude<FullConfigPublic['webServer'], null>[];
|
||||||
_globalScripts: string | RegExp | (string | RegExp)[] | null;
|
|
||||||
|
|
||||||
// This is an ephemeral project that is not added to `projects` list below.
|
|
||||||
_globalProject: FullProjectInternal;
|
|
||||||
|
|
||||||
// Overrides the public field.
|
// Overrides the public field.
|
||||||
projects: FullProjectInternal[];
|
projects: FullProjectInternal[];
|
||||||
|
|
@ -71,7 +67,6 @@ export interface FullProjectInternal extends FullProjectPublic {
|
||||||
_fullyParallel: boolean;
|
_fullyParallel: boolean;
|
||||||
_expect: Project['expect'];
|
_expect: Project['expect'];
|
||||||
_respectGitIgnore: boolean;
|
_respectGitIgnore: boolean;
|
||||||
_setupMatch: string | RegExp | (string | RegExp)[];
|
|
||||||
snapshotPathTemplate: string;
|
snapshotPathTemplate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import { colors, rimraf } from 'playwright-core/lib/utilsBundle';
|
import { colors, rimraf } from 'playwright-core/lib/utilsBundle';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import { debugTest, formatLocation, relativeFilePath, serializeError } from './util';
|
import { debugTest, formatLocation, relativeFilePath, serializeError } from './util';
|
||||||
import type { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, TeardownErrorsPayload, WatchTestResolvedPayload } from './ipc';
|
import type { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, TeardownErrorsPayload } from './ipc';
|
||||||
import { setCurrentTestInfo } from './globals';
|
import { setCurrentTestInfo } from './globals';
|
||||||
import { ConfigLoader } from './configLoader';
|
import { ConfigLoader } from './configLoader';
|
||||||
import type { Suite, TestCase } from './test';
|
import type { Suite, TestCase } from './test';
|
||||||
|
|
@ -171,11 +171,7 @@ export class WorkerRunner extends ProcessRunner {
|
||||||
|
|
||||||
this._configLoader = await ConfigLoader.deserialize(this._params.loader);
|
this._configLoader = await ConfigLoader.deserialize(this._params.loader);
|
||||||
this._testLoader = new TestLoader(this._configLoader.fullConfig());
|
this._testLoader = new TestLoader(this._configLoader.fullConfig());
|
||||||
const globalProject = this._configLoader.fullConfig()._globalProject;
|
this._project = this._configLoader.fullConfig().projects.find(p => p._id === this._params.projectId)!;
|
||||||
if (this._params.projectId === globalProject._id)
|
|
||||||
this._project = globalProject;
|
|
||||||
else
|
|
||||||
this._project = this._configLoader.fullConfig().projects.find(p => p._id === this._params.projectId)!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async runTestGroup(runPayload: RunPayload) {
|
async runTestGroup(runPayload: RunPayload) {
|
||||||
|
|
@ -184,17 +180,8 @@ export class WorkerRunner extends ProcessRunner {
|
||||||
let fatalUnknownTestIds;
|
let fatalUnknownTestIds;
|
||||||
try {
|
try {
|
||||||
await this._loadIfNeeded();
|
await this._loadIfNeeded();
|
||||||
const fileSuite = await this._testLoader.loadTestFile(runPayload.file, 'worker', runPayload.phase);
|
const fileSuite = await this._testLoader.loadTestFile(runPayload.file, 'worker');
|
||||||
const suite = this._testLoader.buildFileSuiteForProject(this._project, fileSuite, this._params.repeatEachIndex, test => {
|
const suite = this._testLoader.buildFileSuiteForProject(this._project, fileSuite, this._params.repeatEachIndex, test => {
|
||||||
if (runPayload.watchMode) {
|
|
||||||
const testResolvedPayload: WatchTestResolvedPayload = {
|
|
||||||
testId: test.id,
|
|
||||||
title: test.title,
|
|
||||||
location: test.location
|
|
||||||
};
|
|
||||||
this.dispatchEvent('watchTestResolved', testResolvedPayload);
|
|
||||||
entries.set(test.id, { testId: test.id, retry: 0 });
|
|
||||||
}
|
|
||||||
if (!entries.has(test.id))
|
if (!entries.has(test.id))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
84
packages/playwright-test/types/test.d.ts
vendored
84
packages/playwright-test/types/test.d.ts
vendored
|
|
@ -190,10 +190,7 @@ export interface FullProject<TestArgs = {}, WorkerArgs = {}> {
|
||||||
/**
|
/**
|
||||||
* Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of
|
* 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
|
* [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. This filter and its command line
|
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option.
|
||||||
* 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).
|
* `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests).
|
||||||
*/
|
*/
|
||||||
|
|
@ -634,22 +631,6 @@ interface TestConfig {
|
||||||
*/
|
*/
|
||||||
fullyParallel?: boolean;
|
fullyParallel?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Files that contain global setup/teardown hooks.
|
|
||||||
*
|
|
||||||
* **Details**
|
|
||||||
*
|
|
||||||
* [test.beforeAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-before-all) hooks in the matching
|
|
||||||
* files will run before testing starts.
|
|
||||||
* [test.afterAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-all) hooks in the matching
|
|
||||||
* files will run after testing finishes.
|
|
||||||
*
|
|
||||||
* If global setup fails, test execution will be skipped.
|
|
||||||
* [test.afterAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-all) hooks will run in the same
|
|
||||||
* worker process as [test.beforeAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-before-all).
|
|
||||||
*/
|
|
||||||
globalScripts?: string|RegExp|Array<string|RegExp>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to the global setup file. This file will be required and run before all the tests. It must export a single
|
* Path to the global setup file. This file will be required and run before all the tests. It must export a single
|
||||||
* function that takes a [`TestConfig`] argument.
|
* function that takes a [`TestConfig`] argument.
|
||||||
|
|
@ -3298,35 +3279,6 @@ type ConnectOptions = {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Playwright Test provides a global `store` object for passing values between project setup and tests. It is an error
|
|
||||||
* to call store methods outside of setup and tests.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* import { setup, store } from '@playwright/test';
|
|
||||||
*
|
|
||||||
* setup('sign in', async ({ page, context }) => {
|
|
||||||
* // Save signed-in state to an entry named 'github-test-user'.
|
|
||||||
* const contextState = await context.storageState();
|
|
||||||
* await store.set('test-user', contextState)
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export interface TestStore {
|
|
||||||
/**
|
|
||||||
* Get named item from the store. Returns undefined if there is no value with given name.
|
|
||||||
* @param name Item name.
|
|
||||||
*/
|
|
||||||
get<T>(name: string): Promise<T | undefined>;
|
|
||||||
/**
|
|
||||||
* Set value to the store.
|
|
||||||
* @param name Item name.
|
|
||||||
* @param value Item value. The value must be serializable to JSON. Passing `undefined` deletes the entry with given name.
|
|
||||||
*/
|
|
||||||
set<T>(name: string, value: T | undefined): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
|
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
|
||||||
*
|
*
|
||||||
|
|
@ -3560,16 +3512,6 @@ export interface PlaywrightTestOptions {
|
||||||
* Either a path to the file with saved storage, or an object with the following fields:
|
* Either a path to the file with saved storage, or an object with the following fields:
|
||||||
*/
|
*/
|
||||||
storageState: StorageState | undefined;
|
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
|
* 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)
|
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
|
||||||
|
|
@ -3877,7 +3819,6 @@ export default test;
|
||||||
|
|
||||||
export const _baseTest: TestType<{}, {}>;
|
export const _baseTest: TestType<{}, {}>;
|
||||||
export const expect: Expect;
|
export const expect: Expect;
|
||||||
export const store: TestStore;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines Playwright config
|
* Defines Playwright config
|
||||||
|
|
@ -5198,10 +5139,7 @@ interface TestProject {
|
||||||
/**
|
/**
|
||||||
* Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of
|
* 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
|
* [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. This filter and its command line
|
* and in the [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option.
|
||||||
* 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).
|
* `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests).
|
||||||
*/
|
*/
|
||||||
|
|
@ -5217,24 +5155,6 @@ interface TestProject {
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Project setup files that will be executed before all tests in the project.
|
|
||||||
*
|
|
||||||
* **Details**
|
|
||||||
*
|
|
||||||
* If project setup fails the tests in this project will be skipped. All project setup files will run in every shard
|
|
||||||
* if the project is sharded. [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep)
|
|
||||||
* and [testProject.grepInvert](https://playwright.dev/docs/api/class-testproject#test-project-grep-invert) and their
|
|
||||||
* command line counterparts also apply to the setup files. If such filters match only tests in the project,
|
|
||||||
* Playwright will run **all** setup files before running the matching tests.
|
|
||||||
*
|
|
||||||
* If there is a file that matches both
|
|
||||||
* [testProject.setupMatch](https://playwright.dev/docs/api/class-testproject#test-project-setup-match) and
|
|
||||||
* [testProject.testMatch](https://playwright.dev/docs/api/class-testproject#test-project-test-match) filters an error
|
|
||||||
* will be thrown.
|
|
||||||
*/
|
|
||||||
setupMatch?: string|RegExp|Array<string|RegExp>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to
|
* 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).
|
* [testProject.testDir](https://playwright.dev/docs/api/class-testproject#test-project-test-dir).
|
||||||
|
|
|
||||||
|
|
@ -478,41 +478,3 @@ test('should have correct types for the config', async ({ runTSC }) => {
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
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`);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test.fixme(true, 'Restore this');
|
||||||
|
|
||||||
type Timeline = { titlePath: string[], event: 'begin' | 'end' }[];
|
type Timeline = { titlePath: string[], event: 'begin' | 'end' }[];
|
||||||
|
|
||||||
function formatTimeline(timeline: Timeline) {
|
function formatTimeline(timeline: Timeline) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import type { PlaywrightTestConfig, TestInfo, PlaywrightTestProject } from '@pla
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test.fixme(true, 'Restore this');
|
||||||
|
|
||||||
function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTemplates?: { [name: string]: PlaywrightTestProject }): Record<string, string> {
|
function createConfigWithProjects(names: string[], testInfo: TestInfo, projectTemplates?: { [name: string]: PlaywrightTestProject }): Record<string, string> {
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
projects: names.map(name => ({ ...projectTemplates?.[name], name, testDir: testInfo.outputPath(name) })),
|
projects: names.map(name => ({ ...projectTemplates?.[name], name, testDir: testInfo.outputPath(name) })),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
import { expect, test } from './playwright-test-fixtures';
|
import { expect, test } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test.fixme(true, 'Restore this');
|
||||||
|
|
||||||
test('should provide store fixture', async ({ runInlineTest }) => {
|
test('should provide store fixture', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
|
|
|
||||||
|
|
@ -188,18 +188,3 @@ test('config should allow void/empty options', async ({ runTSC }) => {
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should provide store interface', async ({ runTSC }) => {
|
|
||||||
const result = await runTSC({
|
|
||||||
'a.spec.ts': `
|
|
||||||
const { test, store } = pwt;
|
|
||||||
test('my test', async () => {
|
|
||||||
await store.set('foo', 'bar');
|
|
||||||
const val = await store.get('foo');
|
|
||||||
// @ts-expect-error
|
|
||||||
await store.unknown();
|
|
||||||
});
|
|
||||||
`
|
|
||||||
});
|
|
||||||
expect(result.exitCode).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
7
utils/generate_types/overrides-test.d.ts
vendored
7
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -196,11 +196,6 @@ type ConnectOptions = {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TestStore {
|
|
||||||
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;
|
||||||
|
|
@ -234,7 +229,6 @@ export interface PlaywrightTestOptions {
|
||||||
permissions: string[] | undefined;
|
permissions: string[] | undefined;
|
||||||
proxy: Proxy | undefined;
|
proxy: Proxy | undefined;
|
||||||
storageState: StorageState | undefined;
|
storageState: StorageState | undefined;
|
||||||
storageStateName: string | undefined;
|
|
||||||
timezoneId: string | undefined;
|
timezoneId: string | undefined;
|
||||||
userAgent: string | undefined;
|
userAgent: string | undefined;
|
||||||
viewport: ViewportSize | null | undefined;
|
viewport: ViewportSize | null | undefined;
|
||||||
|
|
@ -357,7 +351,6 @@ export default test;
|
||||||
|
|
||||||
export const _baseTest: TestType<{}, {}>;
|
export const _baseTest: TestType<{}, {}>;
|
||||||
export const expect: Expect;
|
export const expect: Expect;
|
||||||
export const store: TestStore;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines Playwright config
|
* Defines Playwright config
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue