fix(test): do not allow mixing tests from different types (#29284)
Fixes https://github.com/microsoft/playwright/issues/29282
This commit is contained in:
parent
a89dbe3813
commit
4784139bb0
|
|
@ -54,10 +54,12 @@ export class Suite extends Base implements SuitePrivate {
|
||||||
_fullProject: FullProjectInternal | undefined;
|
_fullProject: FullProjectInternal | undefined;
|
||||||
_fileId: string | undefined;
|
_fileId: string | undefined;
|
||||||
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
readonly _type: 'root' | 'project' | 'file' | 'describe';
|
||||||
|
readonly _testTypeImpl: TestTypeImpl | undefined;
|
||||||
|
|
||||||
constructor(title: string, type: 'root' | 'project' | 'file' | 'describe') {
|
constructor(title: string, type: 'root' | 'project' | 'file' | 'describe', testTypeImpl?: TestTypeImpl) {
|
||||||
super(title);
|
super(title);
|
||||||
this._type = type;
|
this._type = type;
|
||||||
|
this._testTypeImpl = testTypeImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get suites(): Suite[] {
|
get suites(): Suite[] {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export class TestTypeImpl {
|
||||||
test.only = wrapFunctionWithLocation(this._createTest.bind(this, 'only'));
|
test.only = wrapFunctionWithLocation(this._createTest.bind(this, 'only'));
|
||||||
test.describe = wrapFunctionWithLocation(this._describe.bind(this, 'default'));
|
test.describe = wrapFunctionWithLocation(this._describe.bind(this, 'default'));
|
||||||
test.describe.only = wrapFunctionWithLocation(this._describe.bind(this, 'only'));
|
test.describe.only = wrapFunctionWithLocation(this._describe.bind(this, 'only'));
|
||||||
test.describe.configure = wrapFunctionWithLocation(this._configure.bind(this));
|
test.describe.configure = this._configure.bind(this);
|
||||||
test.describe.fixme = wrapFunctionWithLocation(this._describe.bind(this, 'fixme'));
|
test.describe.fixme = wrapFunctionWithLocation(this._describe.bind(this, 'fixme'));
|
||||||
test.describe.parallel = wrapFunctionWithLocation(this._describe.bind(this, 'parallel'));
|
test.describe.parallel = wrapFunctionWithLocation(this._describe.bind(this, 'parallel'));
|
||||||
test.describe.parallel.only = wrapFunctionWithLocation(this._describe.bind(this, 'parallel.only'));
|
test.describe.parallel.only = wrapFunctionWithLocation(this._describe.bind(this, 'parallel.only'));
|
||||||
|
|
@ -53,7 +53,7 @@ export class TestTypeImpl {
|
||||||
test.fixme = wrapFunctionWithLocation(this._modifier.bind(this, 'fixme'));
|
test.fixme = wrapFunctionWithLocation(this._modifier.bind(this, 'fixme'));
|
||||||
test.fail = wrapFunctionWithLocation(this._modifier.bind(this, 'fail'));
|
test.fail = wrapFunctionWithLocation(this._modifier.bind(this, 'fail'));
|
||||||
test.slow = wrapFunctionWithLocation(this._modifier.bind(this, 'slow'));
|
test.slow = wrapFunctionWithLocation(this._modifier.bind(this, 'slow'));
|
||||||
test.setTimeout = wrapFunctionWithLocation(this._setTimeout.bind(this));
|
test.setTimeout = this._setTimeout.bind(this);
|
||||||
test.step = this._step.bind(this);
|
test.step = 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));
|
||||||
|
|
@ -66,7 +66,7 @@ export class TestTypeImpl {
|
||||||
this.test = test;
|
this.test = test;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _currentSuite(location: Location, title: string): Suite | undefined {
|
private _currentSuite(title: string): Suite | undefined {
|
||||||
const suite = currentlyLoadingFileSuite();
|
const suite = currentlyLoadingFileSuite();
|
||||||
if (!suite) {
|
if (!suite) {
|
||||||
throw new Error([
|
throw new Error([
|
||||||
|
|
@ -78,12 +78,18 @@ export class TestTypeImpl {
|
||||||
` when one of the dependencies in your package.json depends on @playwright/test.`,
|
` when one of the dependencies in your package.json depends on @playwright/test.`,
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
}
|
}
|
||||||
|
if (suite._testTypeImpl && suite._testTypeImpl !== this) {
|
||||||
|
throw new Error([
|
||||||
|
`Can't call ${title} inside a describe() suite of a different test type.`,
|
||||||
|
`Make sure to use the same "test" function (created by the test.extend() call) for all declarations inside a suite.`,
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail', location: Location, title: string, fn: Function) {
|
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail', location: Location, title: string, fn: Function) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, 'test()');
|
const suite = this._currentSuite('test()');
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
const test = new TestCase(title, fn, this, location);
|
const test = new TestCase(title, fn, this, location);
|
||||||
|
|
@ -98,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()');
|
const suite = this._currentSuite('test.describe()');
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -107,7 +113,7 @@ export class TestTypeImpl {
|
||||||
title = '';
|
title = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = new Suite(title, 'describe');
|
const child = new Suite(title, 'describe', this);
|
||||||
child._requireFile = suite._requireFile;
|
child._requireFile = suite._requireFile;
|
||||||
child.location = location;
|
child.location = location;
|
||||||
suite._addSuite(child);
|
suite._addSuite(child);
|
||||||
|
|
@ -134,7 +140,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, title: string | Function, fn?: Function) {
|
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, title: string | Function, fn?: Function) {
|
||||||
const suite = this._currentSuite(location, `test.${name}()`);
|
const suite = this._currentSuite(`test.${name}()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
if (typeof title === 'function') {
|
if (typeof title === 'function') {
|
||||||
|
|
@ -145,9 +151,9 @@ export class TestTypeImpl {
|
||||||
suite._hooks.push({ type: name, fn: fn!, title, location });
|
suite._hooks.push({ type: name, fn: fn!, title, location });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
private _configure(options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, `test.describe.configure()`);
|
const suite = this._currentSuite(`test.describe.configure()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -198,7 +204,7 @@ export class TestTypeImpl {
|
||||||
testInfo[type](...modifierArgs as [any, any]);
|
testInfo[type](...modifierArgs as [any, any]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setTimeout(location: Location, timeout: number) {
|
private _setTimeout(timeout: number) {
|
||||||
const suite = currentlyLoadingFileSuite();
|
const suite = currentlyLoadingFileSuite();
|
||||||
if (suite) {
|
if (suite) {
|
||||||
suite._timeout = timeout;
|
suite._timeout = timeout;
|
||||||
|
|
@ -212,7 +218,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _use(location: Location, fixtures: Fixtures) {
|
private _use(location: Location, fixtures: Fixtures) {
|
||||||
const suite = this._currentSuite(location, `test.use()`);
|
const suite = this._currentSuite(`test.use()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
suite._use.push({ fixtures, location });
|
suite._use.push({ fixtures, location });
|
||||||
|
|
|
||||||
|
|
@ -550,3 +550,22 @@ test('should support describe.fixme', async ({ runInlineTest }) => {
|
||||||
expect(result.skipped).toBe(3);
|
expect(result.skipped).toBe(3);
|
||||||
expect(result.output).toContain('heytest4');
|
expect(result.output).toContain('heytest4');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not allow mixing test types', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'mixed.spec.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
|
export const test2 = test.extend({
|
||||||
|
value: 42,
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("test1 suite", () => {
|
||||||
|
test2("test 2", async () => {});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.output).toContain(`Can't call test() inside a describe() suite of a different test type.`);
|
||||||
|
expect(result.output).toContain('> 9 | test2(');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue