fix(test runner): do not run automatic fixtures for beforeAll hooks (#14104)
There are a few issues this covers: - Some fixtures like `page` and `context` are not allowed in `beforeAll` hooks, so using them in automatic fixture makes it throw. - Running automatic fixture solely for `afterAll` is unexpected. This currently happens when `afterAll` is run for cleanup after fixture timeout/throw. For built-in playwright fixture, we keep `'all-hooks-included'` auto mode. Added a doc explaining the execution order.
This commit is contained in:
parent
f7adbd83ee
commit
c3beb71b07
|
|
@ -609,3 +609,144 @@ const config: PlaywrightTestConfig<MyOptions> = {
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Execution order
|
||||||
|
|
||||||
|
Each fixture has a setup and teardown phase separated by the `await use()` call in the fixture. Setup is executed before the fixture is used by the test/hook, and teardown is executed when the fixture will not be used by the test/hook anymore.
|
||||||
|
|
||||||
|
Fixtures follow these rules to determine the execution order:
|
||||||
|
* When fixture A depends on fixture B: B is always set up before A and teared down after A.
|
||||||
|
* Non-automatic fixtures are executed lazily, only when the test/hook needs them.
|
||||||
|
* Test-scoped fixtures are teared down after each test, while worker-scoped fixtures are only teared down when the worker process executing tests is shutdown.
|
||||||
|
|
||||||
|
Consider the following example:
|
||||||
|
|
||||||
|
```js js-flavor=js
|
||||||
|
const { test: base } = require('@playwright/test');
|
||||||
|
|
||||||
|
const test = base.extend({
|
||||||
|
workerFixture: [async ({ browser }) => {
|
||||||
|
// workerFixture setup...
|
||||||
|
await use('workerFixture');
|
||||||
|
// workerFixture teardown...
|
||||||
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
autoWorkerFixture: [async ({ browser }) => {
|
||||||
|
// autoWorkerFixture setup...
|
||||||
|
await use('autoWorkerFixture');
|
||||||
|
// autoWorkerFixture teardown...
|
||||||
|
}, { scope: 'worker', auto: true }],
|
||||||
|
|
||||||
|
testFixture: [async ({ page, workerFixture }) => {
|
||||||
|
// testFixture setup...
|
||||||
|
await use('testFixture');
|
||||||
|
// testFixture teardown...
|
||||||
|
}, { scope: 'test' }],
|
||||||
|
|
||||||
|
autoTestFixture: [async () => {
|
||||||
|
// autoTestFixture setup...
|
||||||
|
await use('autoTestFixture');
|
||||||
|
// autoTestFixture teardown...
|
||||||
|
}, { scope: 'test', auto: true }],
|
||||||
|
|
||||||
|
unusedFixture: [async ({ page }) => {
|
||||||
|
// unusedFixture setup...
|
||||||
|
await use('unusedFixture');
|
||||||
|
// unusedFixture teardown...
|
||||||
|
}, { scope: 'test' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll(async () => { /* ... */ });
|
||||||
|
test.beforeEach(async ({ page }) => { /* ... */ });
|
||||||
|
test('first test', async ({ page }) => { /* ... */ });
|
||||||
|
test('second test', async ({ testFixture }) => { /* ... */ });
|
||||||
|
test.afterEach(async () => { /* ... */ });
|
||||||
|
test.afterAll(async () => { /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
```js js-flavor=ts
|
||||||
|
import { test as base } from '@playwright/test';
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
testFixture: string,
|
||||||
|
autoTestFixture: string,
|
||||||
|
unusedFixture: string,
|
||||||
|
}, {
|
||||||
|
workerFixture: string,
|
||||||
|
autoWorkerFixture: string,
|
||||||
|
}>({
|
||||||
|
workerFixture: [async ({ browser }) => {
|
||||||
|
// workerFixture setup...
|
||||||
|
await use('workerFixture');
|
||||||
|
// workerFixture teardown...
|
||||||
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
autoWorkerFixture: [async ({ browser }) => {
|
||||||
|
// autoWorkerFixture setup...
|
||||||
|
await use('autoWorkerFixture');
|
||||||
|
// autoWorkerFixture teardown...
|
||||||
|
}, { scope: 'worker', auto: true }],
|
||||||
|
|
||||||
|
testFixture: [async ({ page, workerFixture }) => {
|
||||||
|
// testFixture setup...
|
||||||
|
await use('testFixture');
|
||||||
|
// testFixture teardown...
|
||||||
|
}, { scope: 'test' }],
|
||||||
|
|
||||||
|
autoTestFixture: [async () => {
|
||||||
|
// autoTestFixture setup...
|
||||||
|
await use('autoTestFixture');
|
||||||
|
// autoTestFixture teardown...
|
||||||
|
}, { scope: 'test', auto: true }],
|
||||||
|
|
||||||
|
unusedFixture: [async ({ page }) => {
|
||||||
|
// unusedFixture setup...
|
||||||
|
await use('unusedFixture');
|
||||||
|
// unusedFixture teardown...
|
||||||
|
}, { scope: 'test' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll(async () => { /* ... */ });
|
||||||
|
test.beforeEach(async ({ page }) => { /* ... */ });
|
||||||
|
test('first test', async ({ page }) => { /* ... */ });
|
||||||
|
test('second test', async ({ testFixture }) => { /* ... */ });
|
||||||
|
test.afterEach(async () => { /* ... */ });
|
||||||
|
test.afterAll(async () => { /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
Normally, if all tests pass and no errors are thrown, the order of execution is as following.
|
||||||
|
* worker setup and `beforeAll` section:
|
||||||
|
* `browser` setup because it is required by `autoWorkerFixture`.
|
||||||
|
* `autoWorkerFixture` setup because automatic worker fixtures are always set up before anything else.
|
||||||
|
* `beforeAll` runs.
|
||||||
|
* `first test` section:
|
||||||
|
* `autoTestFixture` setup because automatic test fixtures are always set up before test and `beforeEach` hooks.
|
||||||
|
* `page` setup because it is required in `beforeEach` hook.
|
||||||
|
* `beforeEach` runs.
|
||||||
|
* `first test` runs.
|
||||||
|
* `afterEach` runs.
|
||||||
|
* `page` teardown because it is a test-scoped fixture and should be teared down after the test finishes.
|
||||||
|
* `autoTestFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes.
|
||||||
|
* `second test` section:
|
||||||
|
* `autoTestFixture` setup because automatic test fixtures are always set up before test and `beforeEach` hooks.
|
||||||
|
* `page` setup because it is required in `beforeEach` hook.
|
||||||
|
* `beforeEach` runs.
|
||||||
|
* `workerFixture` setup because it is required by `testFixture` that is required by the `second test`.
|
||||||
|
* `testFixture` setup because it is required by the `second test`.
|
||||||
|
* `second test` runs.
|
||||||
|
* `afterEach` runs.
|
||||||
|
* `testFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes.
|
||||||
|
* `page` teardown because it is a test-scoped fixture and should be teared down after the test finishes.
|
||||||
|
* `autoTestFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes.
|
||||||
|
* `afterAll` and worker teardown section:
|
||||||
|
* `afterAll` runs.
|
||||||
|
* `workerFixture` teardown because it is a workers-scoped fixture and should be teared down once at the end.
|
||||||
|
* `autoWorkerFixture` teardown because it is a workers-scoped fixture and should be teared down once at the end.
|
||||||
|
* `browser` teardown because it is a workers-scoped fixture and should be teared down once at the end.
|
||||||
|
|
||||||
|
A few observations:
|
||||||
|
* `page` and `autoTestFixture` are set up and teared down for each test, as test-scoped fixtures.
|
||||||
|
* `unusedFixture` is never set up because it is not used by any tests/hooks.
|
||||||
|
* `testFixture` depends on `workerFixture` and triggers its setup.
|
||||||
|
* `workerFixture` is lazily set up before the second test, but teared down once during worker shutdown, as a worker-scoped fixture.
|
||||||
|
* `autoWorkerFixture` is set up for `beforeAll` hook, but `autoTestFixture` is not.
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ import type { TestInfoImpl } from './testInfo';
|
||||||
import type { FixtureDescription, TimeoutManager } from './timeoutManager';
|
import type { FixtureDescription, TimeoutManager } from './timeoutManager';
|
||||||
|
|
||||||
type FixtureScope = 'test' | 'worker';
|
type FixtureScope = 'test' | 'worker';
|
||||||
|
type FixtureAuto = boolean | 'all-hooks-included';
|
||||||
const kScopeOrder: FixtureScope[] = ['test', 'worker'];
|
const kScopeOrder: FixtureScope[] = ['test', 'worker'];
|
||||||
type FixtureOptions = { auto?: boolean, scope?: FixtureScope, option?: boolean, timeout?: number | undefined };
|
type FixtureOptions = { auto?: FixtureAuto, scope?: FixtureScope, option?: boolean, timeout?: number | undefined };
|
||||||
type FixtureTuple = [ value: any, options: FixtureOptions ];
|
type FixtureTuple = [ value: any, options: FixtureOptions ];
|
||||||
type FixtureRegistration = {
|
type FixtureRegistration = {
|
||||||
// Fixture registration location.
|
// Fixture registration location.
|
||||||
|
|
@ -34,7 +35,7 @@ type FixtureRegistration = {
|
||||||
// Either a fixture function, or a fixture value.
|
// Either a fixture function, or a fixture value.
|
||||||
fn: Function | any;
|
fn: Function | any;
|
||||||
// Auto fixtures always run without user explicitly mentioning them.
|
// Auto fixtures always run without user explicitly mentioning them.
|
||||||
auto: boolean;
|
auto: FixtureAuto;
|
||||||
// An "option" fixture can have a value set in the config.
|
// An "option" fixture can have a value set in the config.
|
||||||
option: boolean;
|
option: boolean;
|
||||||
// Custom title to be used instead of the name, internal-only.
|
// Custom title to be used instead of the name, internal-only.
|
||||||
|
|
@ -160,10 +161,10 @@ export class FixturePool {
|
||||||
for (const entry of Object.entries(fixtures)) {
|
for (const entry of Object.entries(fixtures)) {
|
||||||
const name = entry[0];
|
const name = entry[0];
|
||||||
let value = entry[1];
|
let value = entry[1];
|
||||||
let options: { auto: boolean, scope: FixtureScope, option: boolean, timeout: number | undefined, customTitle: string | undefined } | undefined;
|
let options: { auto: FixtureAuto, scope: FixtureScope, option: boolean, timeout: number | undefined, customTitle: string | undefined } | undefined;
|
||||||
if (isFixtureTuple(value)) {
|
if (isFixtureTuple(value)) {
|
||||||
options = {
|
options = {
|
||||||
auto: !!value[1].auto,
|
auto: value[1].auto ?? false,
|
||||||
scope: value[1].scope || 'test',
|
scope: value[1].scope || 'test',
|
||||||
option: !!value[1].option,
|
option: !!value[1].option,
|
||||||
timeout: value[1].timeout,
|
timeout: value[1].timeout,
|
||||||
|
|
@ -293,11 +294,17 @@ export class FixtureRunner {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveParametersForFunction(fn: Function, testInfo: TestInfoImpl): Promise<object> {
|
async resolveParametersForFunction(fn: Function, testInfo: TestInfoImpl, autoFixtures: 'worker' | 'test' | 'all-hooks-only'): Promise<object> {
|
||||||
// Install all automatic fixtures.
|
// Install automatic fixtures.
|
||||||
for (const registration of this.pool!.registrations.values()) {
|
for (const registration of this.pool!.registrations.values()) {
|
||||||
const shouldSkip = !testInfo && registration.scope === 'test';
|
if (registration.auto === false)
|
||||||
if (registration.auto && !shouldSkip)
|
continue;
|
||||||
|
let shouldRun = true;
|
||||||
|
if (autoFixtures === 'all-hooks-only')
|
||||||
|
shouldRun = registration.scope === 'worker' || registration.auto === 'all-hooks-included';
|
||||||
|
else if (autoFixtures === 'worker')
|
||||||
|
shouldRun = registration.scope === 'worker';
|
||||||
|
if (shouldRun)
|
||||||
await this.setupFixtureForRegistration(registration, testInfo);
|
await this.setupFixtureForRegistration(registration, testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,8 +319,8 @@ export class FixtureRunner {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveParametersAndRunFunction(fn: Function, testInfo: TestInfoImpl) {
|
async resolveParametersAndRunFunction(fn: Function, testInfo: TestInfoImpl, autoFixtures: 'worker' | 'test' | 'all-hooks-only') {
|
||||||
const params = await this.resolveParametersForFunction(fn, testInfo);
|
const params = await this.resolveParametersForFunction(fn, testInfo, autoFixtures);
|
||||||
return fn(params, testInfo);
|
return fn(params, testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -429,7 +429,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
else
|
else
|
||||||
await fs.promises.unlink(file).catch(() => {});
|
await fs.promises.unlink(file).catch(() => {});
|
||||||
}));
|
}));
|
||||||
}, { auto: true, _title: 'built-in playwright configuration' } as any],
|
}, { auto: 'all-hooks-included', _title: 'built-in playwright configuration' } as any],
|
||||||
|
|
||||||
_contextFactory: [async ({ browser, video, _artifactsDir }, use, testInfo) => {
|
_contextFactory: [async ({ browser, video, _artifactsDir }, use, testInfo) => {
|
||||||
let videoMode = typeof video === 'string' ? video : video.mode;
|
let videoMode = typeof video === 'string' ? video : video.mode;
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
|
|
||||||
// Setup fixtures required by the test.
|
// Setup fixtures required by the test.
|
||||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'test' });
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test' });
|
||||||
const params = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo);
|
const params = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, 'test');
|
||||||
beforeHooksStep.complete({}); // Report fixture hooks step as completed.
|
beforeHooksStep.complete({}); // Report fixture hooks step as completed.
|
||||||
|
|
||||||
// Now run the test itself.
|
// Now run the test itself.
|
||||||
|
|
@ -447,7 +447,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
if (actualScope !== scope)
|
if (actualScope !== scope)
|
||||||
continue;
|
continue;
|
||||||
testInfo._timeoutManager.setCurrentRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot });
|
testInfo._timeoutManager.setCurrentRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot });
|
||||||
const result = await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo), {
|
const result = await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo, scope), {
|
||||||
category: 'hook',
|
category: 'hook',
|
||||||
title: `${modifier.type} modifier`,
|
title: `${modifier.type} modifier`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
|
|
@ -472,7 +472,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
// Separate time slot for each "beforeAll" hook.
|
// Separate time slot for each "beforeAll" hook.
|
||||||
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
|
||||||
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
|
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only'), {
|
||||||
category: 'hook',
|
category: 'hook',
|
||||||
title: `${hook.type} hook`,
|
title: `${hook.type} hook`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
|
|
@ -500,7 +500,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
// Separate time slot for each "afterAll" hook.
|
// Separate time slot for each "afterAll" hook.
|
||||||
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
|
||||||
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
|
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only'), {
|
||||||
category: 'hook',
|
category: 'hook',
|
||||||
title: `${hook.type} hook`,
|
title: `${hook.type} hook`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
|
|
@ -519,7 +519,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
for (const hook of hooks) {
|
for (const hook of hooks) {
|
||||||
try {
|
try {
|
||||||
testInfo._timeoutManager.setCurrentRunnable({ type, location: hook.location, slot: timeSlot });
|
testInfo._timeoutManager.setCurrentRunnable({ type, location: hook.location, slot: timeSlot });
|
||||||
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
|
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'test'), {
|
||||||
category: 'hook',
|
category: 'hook',
|
||||||
title: `${hook.type} hook`,
|
title: `${hook.type} hook`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ test('automatic fixtures should work', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
let counterTest = 0;
|
let counterTest = 0;
|
||||||
|
let counterHooksIncluded = 0;
|
||||||
let counterWorker = 0;
|
let counterWorker = 0;
|
||||||
const test = pwt.test;
|
const test = pwt.test;
|
||||||
test.use({
|
test.use({
|
||||||
|
|
@ -305,6 +306,11 @@ test('automatic fixtures should work', async ({ runInlineTest }) => {
|
||||||
await runTest();
|
await runTest();
|
||||||
}, { auto: true } ],
|
}, { auto: true } ],
|
||||||
|
|
||||||
|
automaticTestFixtureHooksIncluded: [ async ({}, runTest) => {
|
||||||
|
++counterHooksIncluded;
|
||||||
|
await runTest();
|
||||||
|
}, { auto: 'all-hooks-included' } ],
|
||||||
|
|
||||||
automaticWorkerFixture: [ async ({}, runTest) => {
|
automaticWorkerFixture: [ async ({}, runTest) => {
|
||||||
++counterWorker;
|
++counterWorker;
|
||||||
await runTest();
|
await runTest();
|
||||||
|
|
@ -312,26 +318,32 @@ test('automatic fixtures should work', async ({ runInlineTest }) => {
|
||||||
});
|
});
|
||||||
test.beforeAll(async ({}) => {
|
test.beforeAll(async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
expect(counterTest).toBe(1);
|
expect(counterHooksIncluded).toBe(1);
|
||||||
|
expect(counterTest).toBe(0);
|
||||||
});
|
});
|
||||||
test.beforeEach(async ({}) => {
|
test.beforeEach(async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
expect(counterTest === 1 || counterTest === 2).toBe(true);
|
expect(counterTest === 1 || counterTest === 2).toBe(true);
|
||||||
|
expect(counterHooksIncluded === 1 || counterHooksIncluded === 2).toBe(true);
|
||||||
});
|
});
|
||||||
test('test 1', async ({}) => {
|
test('test 1', async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
|
expect(counterHooksIncluded).toBe(1);
|
||||||
expect(counterTest).toBe(1);
|
expect(counterTest).toBe(1);
|
||||||
});
|
});
|
||||||
test('test 2', async ({}) => {
|
test('test 2', async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
|
expect(counterHooksIncluded).toBe(2);
|
||||||
expect(counterTest).toBe(2);
|
expect(counterTest).toBe(2);
|
||||||
});
|
});
|
||||||
test.afterEach(async ({}) => {
|
test.afterEach(async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
expect(counterTest === 1 || counterTest === 2).toBe(true);
|
expect(counterTest === 1 || counterTest === 2).toBe(true);
|
||||||
|
expect(counterHooksIncluded === 1 || counterHooksIncluded === 2).toBe(true);
|
||||||
});
|
});
|
||||||
test.afterAll(async ({}) => {
|
test.afterAll(async ({}) => {
|
||||||
expect(counterWorker).toBe(1);
|
expect(counterWorker).toBe(1);
|
||||||
|
expect(counterHooksIncluded).toBe(2);
|
||||||
expect(counterTest).toBe(2);
|
expect(counterTest).toBe(2);
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,41 @@ test('should not save sources when not requested', async ({ runInlineTest }, tes
|
||||||
expect([...resources.keys()].filter(f => f.includes('src@'))).toHaveLength(0);
|
expect([...resources.keys()].filter(f => f.includes('src@'))).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should work in serial mode', async ({ runInlineTest }, testInfo) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = { use: { trace: 'retain-on-failure' } };
|
||||||
|
`,
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test.describe.serial('serial', () => {
|
||||||
|
let page;
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
page = await browser.newPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes', async ({}, testInfo) => {
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails', async ({}, testInfo) => {
|
||||||
|
throw new Error('oh my');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-passes', 'trace.zip'))).toBeFalsy();
|
||||||
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-fails', 'trace.zip'))).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
async function parseTrace(file: string): Promise<Map<string, Buffer>> {
|
async function parseTrace(file: string): Promise<Map<string, Buffer>> {
|
||||||
const zipFS = new ZipFileSystem(file);
|
const zipFS = new ZipFileSystem(file);
|
||||||
const resources = new Map<string, Buffer>();
|
const resources = new Map<string, Buffer>();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue