chore: remove TestProject.projectSetup (#16321)

There are better ideas to address this issue in more general manner.
This commit is contained in:
Dmitry Gozman 2022-08-05 15:24:30 -07:00 committed by GitHub
parent 706c00d242
commit 7a86e140f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 25 additions and 182 deletions

View file

@ -359,8 +359,6 @@ test('test', async ({ page }) => {
}); });
``` ```
You can also have project-specific setup with [`property: TestProject.projectSetup`]. It will only be executed if at least one test from a specific project should be run, while global setup is always executed at the start of the test session.
### Capturing trace of failures during global setup ### Capturing trace of failures during global setup
In some instances, it may be useful to capture a trace of failures encountered during the global setup. In order to do this, you must [start tracing](./api/class-tracing.md#tracing-start) in your setup, and you must ensure that you [stop tracing](./api/class-tracing.md#tracing-stop) if an error occurs before that error is thrown. This can be achieved by wrapping your setup in a `try...catch` block. Here is an example that expands the global setup example to capture a trace. In some instances, it may be useful to capture a trace of failures encountered during the global setup. In order to do this, you must [start tracing](./api/class-tracing.md#tracing-start) in your setup, and you must ensure that you [stop tracing](./api/class-tracing.md#tracing-stop) if an error occurs before that error is thrown. This can be achieved by wrapping your setup in a `try...catch` block. Here is an example that expands the global setup example to capture a trace.

View file

@ -163,63 +163,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.projectSetup
* since: v1.25
- type: ?<[string]>
Path to the project-specifc setup file. This file will be required and run before all the tests from this project. It must export a single function that takes a [`TestConfig`] argument.
Project setup is similar to [`property: TestConfig.globalSetup`], but it is only executed if at least one test from this particular project should be run. Learn more about [global setup and teardown](../test-advanced.md#global-setup-and-teardown).
```js tab=js-js
// playwright.config.js
// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
projects: [
{
name: 'Admin Portal',
projectSetup: './setup-admin',
},
{
name: 'Customer Portal',
projectSetup: './setup-customer',
},
],
};
module.exports = config;
```
```js tab=js-ts
// playwright.config.ts
import { type PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
projects: [
{
name: 'Admin Portal',
projectSetup: './setup-admin',
},
{
name: 'Customer Portal',
projectSetup: './setup-customer',
},
],
};
export default config;
```
## property: TestProject.projectTeardown
* since: v1.25
- type: ?<[string]>
Path to the project-specifc teardown file. This file will be required and run after all the tests from this project. It must export a single function. See also [`property: TestProject.projectSetup`].
Project teardown is similar to [`property: TestConfig.globalTeardown`], but it is only executed if at least one test from this particular project did run. Learn more about [global setup and teardown](../test-advanced.md#global-setup-and-teardown).
## property: TestProject.screenshotsDir ## property: TestProject.screenshotsDir
* since: v1.10 * since: v1.10
* experimental * experimental

View file

@ -257,10 +257,6 @@ export class Loader {
projectConfig.testDir = path.resolve(this._configDir, projectConfig.testDir); projectConfig.testDir = path.resolve(this._configDir, projectConfig.testDir);
if (projectConfig.outputDir !== undefined) if (projectConfig.outputDir !== undefined)
projectConfig.outputDir = path.resolve(this._configDir, projectConfig.outputDir); projectConfig.outputDir = path.resolve(this._configDir, projectConfig.outputDir);
if (projectConfig.projectSetup)
projectConfig.projectSetup = resolveScript(projectConfig.projectSetup, this._configDir);
if (projectConfig.projectTeardown)
projectConfig.projectTeardown = resolveScript(projectConfig.projectTeardown, this._configDir);
if ((projectConfig as any).screenshotsDir !== undefined) if ((projectConfig as any).screenshotsDir !== undefined)
(projectConfig as any).screenshotsDir = path.resolve(this._configDir, (projectConfig as any).screenshotsDir); (projectConfig as any).screenshotsDir = path.resolve(this._configDir, (projectConfig as any).screenshotsDir);
if (projectConfig.snapshotDir !== undefined) if (projectConfig.snapshotDir !== undefined)
@ -285,8 +281,6 @@ export class Loader {
retries: takeFirst(projectConfig.retries, config.retries, 0), retries: takeFirst(projectConfig.retries, config.retries, 0),
metadata: takeFirst(projectConfig.metadata, config.metadata, undefined), metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
name, name,
_projectSetup: projectConfig.projectSetup,
_projectTeardown: projectConfig.projectTeardown,
testDir, testDir,
_respectGitIgnore: respectGitIgnore, _respectGitIgnore: respectGitIgnore,
snapshotDir, snapshotDir,

View file

@ -394,7 +394,7 @@ export class Runner {
// 13. Run Global setup. // 13. Run Global setup.
const result: FullResult = { status: 'passed' }; const result: FullResult = { status: 'passed' };
const globalTearDown = await this._performGlobalAndProjectSetup(config, rootSuite, [...filesByProject.keys()], result); const globalTearDown = await this._performGlobalSetup(config, rootSuite, [...filesByProject.keys()], result);
if (result.status !== 'passed') if (result.status !== 'passed')
return result; return result;
@ -443,7 +443,7 @@ export class Runner {
// 4. Run Global setup. // 4. Run Global setup.
const result: FullResult = { status: 'passed' }; const result: FullResult = { status: 'passed' };
const globalTearDown = await this._performGlobalAndProjectSetup(config, rootSuite, config.projects.filter(p => !options.projectFilter || options.projectFilter.includes(p.name)), result); const globalTearDown = await this._performGlobalSetup(config, rootSuite, config.projects.filter(p => !options.projectFilter || options.projectFilter.includes(p.name)), result);
if (result.status !== 'passed') if (result.status !== 'passed')
return result; return result;
@ -576,43 +576,22 @@ export class Runner {
return true; return true;
} }
private async _performGlobalAndProjectSetup(config: FullConfigInternal, rootSuite: Suite, projects: FullProjectInternal[], result: FullResult): Promise<(() => Promise<void>) | undefined> { private async _performGlobalSetup(config: FullConfigInternal, rootSuite: Suite, projects: FullProjectInternal[], result: FullResult): Promise<(() => Promise<void>) | undefined> {
type SetupData = { let globalSetupResult: any = undefined;
setupFile?: string | null;
teardownFile?: string | null;
setupResult?: any;
};
const setups: SetupData[] = [];
setups.push({
setupFile: config.globalSetup,
teardownFile: config.globalTeardown,
setupResult: undefined,
});
for (const project of projects) {
setups.push({
setupFile: project._projectSetup,
teardownFile: project._projectTeardown,
setupResult: undefined,
});
}
const pluginsThatWereSetUp: TestRunnerPlugin[] = []; const pluginsThatWereSetUp: TestRunnerPlugin[] = [];
const sigintWatcher = new SigIntWatcher(); const sigintWatcher = new SigIntWatcher();
const tearDown = async () => { const tearDown = async () => {
setups.reverse(); await this._runAndReportError(async () => {
for (const setup of setups) { if (globalSetupResult && typeof globalSetupResult === 'function')
await this._runAndReportError(async () => { await globalSetupResult(this._loader.fullConfig());
if (setup.setupResult && typeof setup.setupResult === 'function') }, result);
await setup.setupResult(this._loader.fullConfig());
}, result);
await this._runAndReportError(async () => { await this._runAndReportError(async () => {
if (setup.setupResult && setup.teardownFile) if (globalSetupResult && config.globalTeardown)
await (await this._loader.loadGlobalHook(setup.teardownFile))(this._loader.fullConfig()); await (await this._loader.loadGlobalHook(config.globalTeardown))(this._loader.fullConfig());
}, result); }, result);
}
for (const plugin of pluginsThatWereSetUp.reverse()) { for (const plugin of pluginsThatWereSetUp.reverse()) {
await this._runAndReportError(async () => { await this._runAndReportError(async () => {
@ -637,19 +616,17 @@ export class Runner {
pluginsThatWereSetUp.push(plugin); pluginsThatWereSetUp.push(plugin);
} }
// Then do global setup and project setups. // Then do global setup.
for (const setup of setups) { if (!sigintWatcher.hadSignal()) {
if (!sigintWatcher.hadSignal()) { if (config.globalSetup) {
if (setup.setupFile) { const hook = await this._loader.loadGlobalHook(config.globalSetup);
const hook = await this._loader.loadGlobalHook(setup.setupFile); await Promise.race([
await Promise.race([ Promise.resolve().then(() => hook(this._loader.fullConfig())).then((r: any) => globalSetupResult = r || '<noop>'),
Promise.resolve().then(() => hook(this._loader.fullConfig())).then((r: any) => setup.setupResult = r || '<noop>'), sigintWatcher.promise(),
sigintWatcher.promise(), ]);
]); } else {
} else { // Make sure we run the teardown.
// Make sure we run the teardown. globalSetupResult = '<noop>';
setup.setupResult = '<noop>';
}
} }
} }
}, result); }, result);

View file

@ -68,8 +68,6 @@ export interface FullProjectInternal extends FullProjectPublic {
_expect: Project['expect']; _expect: Project['expect'];
_screenshotsDir: string; _screenshotsDir: string;
_respectGitIgnore: boolean; _respectGitIgnore: boolean;
_projectSetup?: string;
_projectTeardown?: string;
} }
export interface ReporterInternal extends Reporter { export interface ReporterInternal extends Reporter {

View file

@ -4363,49 +4363,6 @@ interface TestProject {
*/ */
name?: string; name?: string;
/**
* Path to the project-specifc setup file. This file will be required and run before all the tests from this project. It
* must export a single function that takes a [`TestConfig`] argument.
*
* Project setup is similar to
* [testConfig.globalSetup](https://playwright.dev/docs/api/class-testconfig#test-config-global-setup), but it is only
* executed if at least one test from this particular project should be run. Learn more about
* [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown).
*
* ```js
* // playwright.config.ts
* import { type PlaywrightTestConfig } from '@playwright/test';
*
* const config: PlaywrightTestConfig = {
* projects: [
* {
* name: 'Admin Portal',
* projectSetup: './setup-admin',
* },
* {
* name: 'Customer Portal',
* projectSetup: './setup-customer',
* },
* ],
* };
* export default config;
* ```
*
*/
projectSetup?: string;
/**
* Path to the project-specifc teardown file. This file will be required and run after all the tests from this project. It
* must export a single function. See also
* [testProject.projectSetup](https://playwright.dev/docs/api/class-testproject#test-project-project-setup).
*
* Project teardown is similar to
* [testConfig.globalTeardown](https://playwright.dev/docs/api/class-testconfig#test-config-global-teardown), but it is
* only executed if at least one test from this particular project did run. Learn more about
* [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown).
*/
projectTeardown?: string;
/** /**
* 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).

View file

@ -416,8 +416,6 @@ test('should have correct types for the config', async ({ runTSC }) => {
projects: [ projects: [
{ {
name: 'project name', name: 'project name',
projectSetup: './projectSetup',
projectTeardown: './projectTeardown',
} }
], ],
}; };

View file

@ -25,8 +25,8 @@ test('globalSetup and globalTeardown should work', async ({ runInlineTest }) =>
globalSetup: './globalSetup', globalSetup: './globalSetup',
globalTeardown: path.join(__dirname, 'globalTeardown.ts'), globalTeardown: path.join(__dirname, 'globalTeardown.ts'),
projects: [ projects: [
{ name: 'p1', projectSetup: './projectSetup1', projectTeardown: './projectTeardown1' }, { name: 'p1' },
{ name: 'p2', projectSetup: './projectSetup2', projectTeardown: './projectTeardown2' }, { name: 'p2' },
] ]
}; };
`, `,
@ -40,26 +40,6 @@ test('globalSetup and globalTeardown should work', async ({ runInlineTest }) =>
console.log('\\n%%from-global-teardown'); console.log('\\n%%from-global-teardown');
}; };
`, `,
'dir/projectSetup1.ts': `
module.exports = async () => {
console.log('\\n%%from-project-setup-1');
};
`,
'dir/projectTeardown1.ts': `
module.exports = async () => {
console.log('\\n%%from-project-teardown-1');
};
`,
'dir/projectSetup2.ts': `
module.exports = async () => {
console.log('\\n%%from-project-setup-2');
};
`,
'dir/projectTeardown2.ts': `
module.exports = async () => {
console.log('\\n%%from-project-teardown-2');
};
`,
'a.test.js': ` 'a.test.js': `
const { test } = pwt; const { test } = pwt;
test('should work', async ({}, testInfo) => { test('should work', async ({}, testInfo) => {
@ -71,9 +51,7 @@ test('globalSetup and globalTeardown should work', async ({ runInlineTest }) =>
expect(result.failed).toBe(0); expect(result.failed).toBe(0);
expect(stripAnsi(result.output).split('\n').filter(line => line.startsWith('%%'))).toEqual([ expect(stripAnsi(result.output).split('\n').filter(line => line.startsWith('%%'))).toEqual([
'%%from-global-setup', '%%from-global-setup',
'%%from-project-setup-2',
'%%from-test', '%%from-test',
'%%from-project-teardown-2',
'%%from-global-teardown', '%%from-global-teardown',
]); ]);
}); });