mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: report more fatal errors via reporter (#19640)
This commit is contained in:
parent
8b80e22a03
commit
233664bd30
@ -20,6 +20,7 @@ import type { FixturesWithLocation, Location, WorkerInfo } from './types';
|
||||
import { ManualPromise } from 'playwright-core/lib/utils/manualPromise';
|
||||
import type { TestInfoImpl } from './testInfo';
|
||||
import type { FixtureDescription, TimeoutManager } from './timeoutManager';
|
||||
import { addFatalError } from './globals';
|
||||
|
||||
type FixtureScope = 'test' | 'worker';
|
||||
type FixtureAuto = boolean | 'all-hooks-included';
|
||||
@ -209,20 +210,28 @@ export class FixturePool {
|
||||
|
||||
const previous = this.registrations.get(name);
|
||||
if (previous && options) {
|
||||
if (previous.scope !== options.scope)
|
||||
throw errorWithLocations(`Fixture "${name}" has already been registered as a { scope: '${previous.scope}' } fixture.`, { location, name }, previous);
|
||||
if (previous.auto !== options.auto)
|
||||
throw errorWithLocations(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture.`, { location, name }, previous);
|
||||
if (previous.scope !== options.scope) {
|
||||
addFatalError(`Fixture "${name}" has already been registered as a { scope: '${previous.scope}' } fixture defined in ${formatLocation(previous.location)}.`, location);
|
||||
continue;
|
||||
}
|
||||
if (previous.auto !== options.auto) {
|
||||
addFatalError(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture defined in ${formatLocation(previous.location)}.`, location);
|
||||
continue;
|
||||
}
|
||||
} else if (previous) {
|
||||
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle };
|
||||
} else if (!options) {
|
||||
options = { auto: false, scope: 'test', option: false, timeout: undefined, customTitle: undefined };
|
||||
}
|
||||
|
||||
if (!kScopeOrder.includes(options.scope))
|
||||
throw errorWithLocations(`Fixture "${name}" has unknown { scope: '${options.scope}' }.`, { location, name });
|
||||
if (options.scope === 'worker' && disallowWorkerFixtures)
|
||||
throw errorWithLocations(`Cannot use({ ${name} }) in a describe group, because it forces a new worker.\nMake it top-level in the test file or put in the configuration file.`, { location, name });
|
||||
if (!kScopeOrder.includes(options.scope)) {
|
||||
addFatalError(`Fixture "${name}" has unknown { scope: '${options.scope}' }.`, location);
|
||||
continue;
|
||||
}
|
||||
if (options.scope === 'worker' && disallowWorkerFixtures) {
|
||||
addFatalError(`Cannot use({ ${name} }) in a describe group, because it forces a new worker.\nMake it top-level in the test file or put in the configuration file.`, location);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Overriding option with "undefined" value means setting it to the default value
|
||||
// from the config or from the original declaration of the option.
|
||||
@ -253,19 +262,23 @@ export class FixturePool {
|
||||
const dep = this.resolveDependency(registration, name);
|
||||
if (!dep) {
|
||||
if (name === registration.name)
|
||||
throw errorWithLocations(`Fixture "${registration.name}" references itself, but does not have a base implementation.`, registration);
|
||||
addFatalError(`Fixture "${registration.name}" references itself, but does not have a base implementation.`, registration.location);
|
||||
else
|
||||
throw errorWithLocations(`Fixture "${registration.name}" has unknown parameter "${name}".`, registration);
|
||||
addFatalError(`Fixture "${registration.name}" has unknown parameter "${name}".`, registration.location);
|
||||
continue;
|
||||
}
|
||||
if (kScopeOrder.indexOf(registration.scope) > kScopeOrder.indexOf(dep.scope)) {
|
||||
addFatalError(`${registration.scope} fixture "${registration.name}" cannot depend on a ${dep.scope} fixture "${name}" defined in ${formatLocation(dep.location)}.`, registration.location);
|
||||
continue;
|
||||
}
|
||||
if (kScopeOrder.indexOf(registration.scope) > kScopeOrder.indexOf(dep.scope))
|
||||
throw errorWithLocations(`${registration.scope} fixture "${registration.name}" cannot depend on a ${dep.scope} fixture "${name}".`, registration, dep);
|
||||
if (!markers.has(dep)) {
|
||||
visit(dep);
|
||||
} else if (markers.get(dep) === 'visiting') {
|
||||
const index = stack.indexOf(dep);
|
||||
const regs = stack.slice(index, stack.length);
|
||||
const names = regs.map(r => `"${r.name}"`);
|
||||
throw errorWithLocations(`Fixtures ${names.join(' -> ')} -> "${dep.name}" form a dependency cycle.`, ...regs);
|
||||
addFatalError(`Fixtures ${names.join(' -> ')} -> "${dep.name}" form a dependency cycle: ${regs.map(r => formatLocation(r.location)).join(' -> ')}`, dep.location);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
markers.set(registration, 'visited');
|
||||
@ -287,7 +300,7 @@ export class FixturePool {
|
||||
for (const name of fixtureParameterNames(fn, location)) {
|
||||
const registration = this.registrations.get(name);
|
||||
if (!registration)
|
||||
throw errorWithLocations(`${prefix} has unknown parameter "${name}".`, { location, name: prefix, quoted: false });
|
||||
addFatalError(`${prefix} has unknown parameter "${name}".`, location);
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,7 +434,7 @@ function innerFixtureParameterNames(fn: Function, location: Location): string[]
|
||||
return [];
|
||||
const [firstParam] = splitByComma(trimmedParams);
|
||||
if (firstParam[0] !== '{' || firstParam[firstParam.length - 1] !== '}')
|
||||
throw errorWithLocations('First argument must use the object destructuring pattern: ' + firstParam, { location });
|
||||
addFatalError('First argument must use the object destructuring pattern: ' + firstParam, location);
|
||||
const props = splitByComma(firstParam.substring(1, firstParam.length - 1)).map(prop => {
|
||||
const colon = prop.indexOf(':');
|
||||
return colon === -1 ? prop : prop.substring(0, colon).trim();
|
||||
@ -469,15 +482,3 @@ function registrationId(registration: FixtureRegistration): string {
|
||||
registration.id = map.get(registration.fn)!;
|
||||
return registration.id;
|
||||
}
|
||||
|
||||
function errorWithLocations(message: string, ...defined: { location: Location, name?: string, quoted?: boolean }[]): Error {
|
||||
for (const { name, location, quoted } of defined) {
|
||||
let prefix = '';
|
||||
if (name && quoted === false)
|
||||
prefix = name + ' ';
|
||||
else if (name)
|
||||
prefix = `"${name}" `;
|
||||
message += `\n ${prefix}defined at ${formatLocation(location)}`;
|
||||
}
|
||||
return new Error(message);
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
import type { TestInfoImpl } from './testInfo';
|
||||
import type { Suite } from './test';
|
||||
import type { TestError, Location } from '../types/testReporter';
|
||||
import { formatLocation } from './util';
|
||||
|
||||
let currentTestInfoValue: TestInfoImpl | null = null;
|
||||
export function setCurrentTestInfo(testInfo: TestInfoImpl | null) {
|
||||
@ -32,3 +34,15 @@ export function setCurrentlyLoadingFileSuite(suite: Suite | undefined) {
|
||||
export function currentlyLoadingFileSuite() {
|
||||
return currentFileSuite;
|
||||
}
|
||||
|
||||
let _fatalErrors: TestError[] | undefined;
|
||||
export function setFatalErrorSink(fatalErrors: TestError[]) {
|
||||
_fatalErrors = fatalErrors;
|
||||
}
|
||||
|
||||
export function addFatalError(message: string, location: Location) {
|
||||
if (_fatalErrors)
|
||||
_fatalErrors.push({ message: `Error: ${message}`, location });
|
||||
else
|
||||
throw new Error(`${formatLocation(location)}: ${message}`);
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import { Suite } from './test';
|
||||
import type { Config, FullConfigInternal, FullProjectInternal, ReporterInternal } from './types';
|
||||
import { createFileMatcher, createFileMatcherFromFilters, createTitleMatcher, serializeError } from './util';
|
||||
import type { Matcher, TestFileFilter } from './util';
|
||||
import { setFatalErrorSink } from './globals';
|
||||
|
||||
const removeFolderAsync = promisify(rimraf);
|
||||
const readDirAsync = promisify(fs.readdir);
|
||||
@ -81,10 +82,12 @@ export class Runner {
|
||||
private _loader: Loader;
|
||||
private _reporter!: ReporterInternal;
|
||||
private _plugins: TestRunnerPlugin[] = [];
|
||||
private _fatalErrors: TestError[] = [];
|
||||
|
||||
constructor(configCLIOverrides?: ConfigCLIOverrides) {
|
||||
this._loader = new Loader(configCLIOverrides);
|
||||
setRunnerToAddPluginsTo(this);
|
||||
setFatalErrorSink(this._fatalErrors);
|
||||
}
|
||||
|
||||
addPlugin(plugin: TestRunnerPlugin) {
|
||||
@ -275,7 +278,7 @@ export class Runner {
|
||||
return { filesByProject, setupFiles };
|
||||
}
|
||||
|
||||
private async _collectTestGroups(options: RunOptions, fatalErrors: TestError[]): Promise<{ rootSuite: Suite, projectSetupGroups: TestGroup[], testGroups: TestGroup[] }> {
|
||||
private async _collectTestGroups(options: RunOptions): Promise<{ rootSuite: Suite, projectSetupGroups: TestGroup[], testGroups: TestGroup[] }> {
|
||||
const config = this._loader.fullConfig();
|
||||
const projects = this._collectProjects(options.projectFilter);
|
||||
const { filesByProject, setupFiles } = await this._collectFiles(projects, options.testFileFilters);
|
||||
@ -292,7 +295,7 @@ export class Runner {
|
||||
result = await this._createFilteredRootSuite(options, filesByProject, setupFiles, false, setupFiles);
|
||||
}
|
||||
|
||||
fatalErrors.push(...result.fatalErrors);
|
||||
this._fatalErrors.push(...result.fatalErrors);
|
||||
const { rootSuite } = result;
|
||||
|
||||
const allTestGroups = createTestGroups(rootSuite.suites, config.workers);
|
||||
@ -442,14 +445,13 @@ export class Runner {
|
||||
|
||||
private async _run(options: RunOptions): Promise<FullResult> {
|
||||
const config = this._loader.fullConfig();
|
||||
const fatalErrors: TestError[] = [];
|
||||
// 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.
|
||||
const { rootSuite, projectSetupGroups, testGroups } = await this._collectTestGroups(options, fatalErrors);
|
||||
const { rootSuite, projectSetupGroups, testGroups } = await this._collectTestGroups(options);
|
||||
|
||||
// Fail when no tests.
|
||||
if (!rootSuite.allTests().length && !options.passWithNoTests)
|
||||
fatalErrors.push(createNoTestsError());
|
||||
this._fatalErrors.push(createNoTestsError());
|
||||
|
||||
this._filterForCurrentShard(rootSuite, projectSetupGroups, testGroups);
|
||||
|
||||
@ -459,8 +461,8 @@ export class Runner {
|
||||
this._reporter.onBegin?.(config, rootSuite);
|
||||
|
||||
// Bail out on errors prior to running global setup.
|
||||
if (fatalErrors.length) {
|
||||
for (const error of fatalErrors)
|
||||
if (this._fatalErrors.length) {
|
||||
for (const error of this._fatalErrors)
|
||||
this._reporter.onError?.(error);
|
||||
return { status: 'failed' };
|
||||
}
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import { expect } from './expect';
|
||||
import { currentlyLoadingFileSuite, currentTestInfo, setCurrentlyLoadingFileSuite } from './globals';
|
||||
import { currentlyLoadingFileSuite, currentTestInfo, addFatalError, setCurrentlyLoadingFileSuite } from './globals';
|
||||
import { TestCase, Suite } from './test';
|
||||
import { wrapFunctionWithLocation } from './transform';
|
||||
import type { Fixtures, FixturesWithLocation, Location, TestType } from './types';
|
||||
import { errorWithLocation, serializeError } from './util';
|
||||
import { serializeError } from './util';
|
||||
|
||||
const testTypeSymbol = Symbol('testType');
|
||||
|
||||
@ -67,29 +67,33 @@ export class TestTypeImpl {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
private _ensureCurrentSuite(location: Location, title: string): Suite {
|
||||
private _currentSuite(location: Location, title: string): Suite | undefined {
|
||||
const suite = currentlyLoadingFileSuite();
|
||||
if (!suite) {
|
||||
throw errorWithLocation(location, [
|
||||
addFatalError([
|
||||
`Playwright Test did not expect ${title} to be called here.`,
|
||||
`Most common reasons include:`,
|
||||
`- You are calling ${title} in a configuration file.`,
|
||||
`- You are calling ${title} in a file that is imported by the configuration file.`,
|
||||
`- You have two different versions of @playwright/test. This usually happens`,
|
||||
` when one of the dependencies in your package.json depends on @playwright/test.`,
|
||||
].join('\n'));
|
||||
].join('\n'), location);
|
||||
return;
|
||||
}
|
||||
if (this._projectSetup !== suite._isProjectSetup) {
|
||||
if (this._projectSetup)
|
||||
throw errorWithLocation(location, `${title} is called in a file which is not a part of project setup.`);
|
||||
throw errorWithLocation(location, `${title} is called in a project setup file (use '_setup' instead of 'test').`);
|
||||
addFatalError(`${title} is called in a file which is not a part of project setup.`, location);
|
||||
else
|
||||
addFatalError(`${title} is called in a project setup file (use '_setup' instead of 'test').`, location);
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
|
||||
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme', location: Location, title: string, fn: Function) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._ensureCurrentSuite(location, this._projectSetup ? '_setup()' : 'test()');
|
||||
const suite = this._currentSuite(location, this._projectSetup ? '_setup()' : 'test()');
|
||||
if (!suite)
|
||||
return;
|
||||
const test = new TestCase(title, fn, this, location);
|
||||
test._requireFile = suite._requireFile;
|
||||
test._isProjectSetup = suite._isProjectSetup;
|
||||
@ -109,7 +113,9 @@ export class TestTypeImpl {
|
||||
|
||||
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, title: string | Function, fn?: Function) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._ensureCurrentSuite(location, this._projectSetup ? 'setup.describe()' : 'test.describe()');
|
||||
const suite = this._currentSuite(location, this._projectSetup ? 'setup.describe()' : 'test.describe()');
|
||||
if (!suite)
|
||||
return;
|
||||
|
||||
if (typeof title === 'function') {
|
||||
fn = title;
|
||||
@ -135,7 +141,7 @@ export class TestTypeImpl {
|
||||
|
||||
for (let parent: Suite | undefined = suite; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === 'serial' && child._parallelMode === 'parallel')
|
||||
throw errorWithLocation(location, 'describe.parallel cannot be nested inside describe.serial');
|
||||
addFatalError('describe.parallel cannot be nested inside describe.serial', location);
|
||||
}
|
||||
|
||||
setCurrentlyLoadingFileSuite(child);
|
||||
@ -144,13 +150,17 @@ export class TestTypeImpl {
|
||||
}
|
||||
|
||||
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, fn: Function) {
|
||||
const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.${name}()`);
|
||||
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.${name}()`);
|
||||
if (!suite)
|
||||
return;
|
||||
suite._hooks.push({ type: name, fn, location });
|
||||
}
|
||||
|
||||
private _configure(location: Location, options: { mode?: 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.describe.configure()`);
|
||||
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.describe.configure()`);
|
||||
if (!suite)
|
||||
return;
|
||||
|
||||
if (options.timeout !== undefined)
|
||||
suite._timeout = options.timeout;
|
||||
@ -160,11 +170,11 @@ export class TestTypeImpl {
|
||||
|
||||
if (options.mode !== undefined) {
|
||||
if (suite._parallelMode !== 'default')
|
||||
throw errorWithLocation(location, 'Parallel mode is already assigned for the enclosing scope.');
|
||||
addFatalError('Parallel mode is already assigned for the enclosing scope.', location);
|
||||
suite._parallelMode = options.mode;
|
||||
for (let parent: Suite | undefined = suite.parent; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === 'serial' && suite._parallelMode === 'parallel')
|
||||
throw errorWithLocation(location, 'describe.parallel cannot be nested inside describe.serial');
|
||||
addFatalError('describe.parallel cannot be nested inside describe.serial', location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,10 +200,12 @@ export class TestTypeImpl {
|
||||
}
|
||||
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw errorWithLocation(location, `test.${type}() can only be called inside test, describe block or fixture`);
|
||||
if (!testInfo) {
|
||||
addFatalError(`test.${type}() can only be called inside test, describe block or fixture`, location);
|
||||
return;
|
||||
}
|
||||
if (typeof modifierArgs[0] === 'function')
|
||||
throw errorWithLocation(location, `test.${type}() with a function can only be called inside describe block`);
|
||||
addFatalError(`test.${type}() with a function can only be called inside describe block`, location);
|
||||
testInfo[type](...modifierArgs as [any, any]);
|
||||
}
|
||||
|
||||
@ -205,20 +217,26 @@ export class TestTypeImpl {
|
||||
}
|
||||
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw errorWithLocation(location, `test.setTimeout() can only be called from a test`);
|
||||
if (!testInfo) {
|
||||
addFatalError(`test.setTimeout() can only be called from a test`, location);
|
||||
return;
|
||||
}
|
||||
testInfo.setTimeout(timeout);
|
||||
}
|
||||
|
||||
private _use(location: Location, fixtures: Fixtures) {
|
||||
const suite = this._ensureCurrentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.use()`);
|
||||
const suite = this._currentSuite(location, `${this._projectSetup ? '_setup' : 'test'}.use()`);
|
||||
if (!suite)
|
||||
return;
|
||||
suite._use.push({ fixtures, location });
|
||||
}
|
||||
|
||||
private async _step<T>(location: Location, title: string, body: () => Promise<T>): Promise<T> {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw errorWithLocation(location, `${this._projectSetup ? '_setup' : 'test'}.step() can only be called from a test`);
|
||||
if (!testInfo) {
|
||||
addFatalError(`${this._projectSetup ? '_setup' : 'test'}.step() can only be called from a test`, location);
|
||||
return undefined as any;
|
||||
}
|
||||
const step = testInfo._addStep({
|
||||
category: 'test.step',
|
||||
title,
|
||||
|
||||
@ -193,10 +193,6 @@ export function errorWithFile(file: string, message: string) {
|
||||
return new Error(`${relativeFilePath(file)}: ${message}`);
|
||||
}
|
||||
|
||||
export function errorWithLocation(location: Location, message: string) {
|
||||
return new Error(`${formatLocation(location)}: ${message}`);
|
||||
}
|
||||
|
||||
export function expectTypes(receiver: any, types: string[], matcherName: string) {
|
||||
if (typeof receiver !== 'object' || !types.includes(receiver.constructor.name)) {
|
||||
const commaSeparated = types.slice();
|
||||
|
||||
@ -128,7 +128,8 @@ test('should throw when using non-defined super worker fixture', async ({ runInl
|
||||
`
|
||||
});
|
||||
expect(result.output).toContain(`Fixture "foo" references itself, but does not have a base implementation.`);
|
||||
expect(result.output).toContain('a.spec.ts:5:29');
|
||||
expect(result.output).toContain('a.spec.ts:5');
|
||||
expect(stripAnsi(result.output)).toContain('const test = pwt.test.extend');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -149,9 +150,9 @@ test('should throw when defining test fixture with the same name as a worker fix
|
||||
test2('works', async ({foo}) => {});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain(`Fixture "foo" has already been registered as a { scope: 'worker' } fixture.`);
|
||||
expect(result.output).toContain(`Fixture "foo" has already been registered as a { scope: 'worker' } fixture defined in e.spec.ts:5:30.`);
|
||||
expect(result.output).toContain(`e.spec.ts:10`);
|
||||
expect(result.output).toContain(`e.spec.ts:5`);
|
||||
expect(stripAnsi(result.output)).toContain('const test2 = test1.extend');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -172,9 +173,9 @@ test('should throw when defining worker fixture with the same name as a test fix
|
||||
test2('works', async ({foo}) => {});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain(`Fixture "foo" has already been registered as a { scope: 'test' } fixture.`);
|
||||
expect(result.output).toContain(`Fixture "foo" has already been registered as a { scope: 'test' } fixture defined in e.spec.ts:5:30.`);
|
||||
expect(result.output).toContain(`e.spec.ts:10`);
|
||||
expect(result.output).toContain(`e.spec.ts:5`);
|
||||
expect(stripAnsi(result.output)).toContain('const test2 = test1.extend');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -194,8 +195,7 @@ test('should throw when worker fixture depends on a test fixture', async ({ runI
|
||||
test('works', async ({bar}) => {});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('worker fixture "bar" cannot depend on a test fixture "foo".');
|
||||
expect(result.output).toContain(`f.spec.ts:5`);
|
||||
expect(result.output).toContain('worker fixture "bar" cannot depend on a test fixture "foo" defined in f.spec.ts:5:29.');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -239,12 +239,8 @@ test('should detect fixture dependency cycle', async ({ runInlineTest }) => {
|
||||
test('works', async ({foo}) => {});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('Fixtures "bar" -> "baz" -> "qux" -> "foo" -> "bar" form a dependency cycle.');
|
||||
expect(result.output).toContain('"foo" defined at');
|
||||
expect(result.output).toContain('"bar" defined at');
|
||||
expect(result.output).toContain('"baz" defined at');
|
||||
expect(result.output).toContain('"qux" defined at');
|
||||
expect(result.output).toContain('x.spec.ts:5:29');
|
||||
expect(result.output).toContain('Fixtures "bar" -> "baz" -> "qux" -> "foo" -> "bar" form a dependency cycle:');
|
||||
expect(result.output).toContain('x.spec.ts:5:29 -> x.spec.ts:5:29 -> x.spec.ts:5:29 -> x.spec.ts:5:29');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -261,7 +257,8 @@ test('should not reuse fixtures from one file in another one', async ({ runInlin
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('Test has unknown parameter "foo".');
|
||||
expect(result.output).toContain('b.spec.ts:7:7');
|
||||
expect(result.output).toContain('b.spec.ts:7');
|
||||
expect(stripAnsi(result.output)).toContain(`test('test2', async ({foo}) => {})`);
|
||||
});
|
||||
|
||||
test('should throw for cycle in two overrides', async ({ runInlineTest }) => {
|
||||
@ -283,9 +280,8 @@ test('should throw for cycle in two overrides', async ({ runInlineTest }) => {
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('Fixtures "bar" -> "foo" -> "bar" form a dependency cycle.');
|
||||
expect(result.output).toContain('a.test.js:9');
|
||||
expect(result.output).toContain('a.test.js:12');
|
||||
expect(result.output).toContain('Fixtures "bar" -> "foo" -> "bar" form a dependency cycle:');
|
||||
expect(result.output).toContain('a.test.js:12:27 -> a.test.js:9:27');
|
||||
});
|
||||
|
||||
test('should throw when overridden worker fixture depends on a test fixture', async ({ runInlineTest }) => {
|
||||
@ -302,7 +298,8 @@ test('should throw when overridden worker fixture depends on a test fixture', as
|
||||
test2('works', async ({bar}) => {});
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('worker fixture "bar" cannot depend on a test fixture "foo".');
|
||||
expect(result.output).toContain('worker fixture "bar" cannot depend on a test fixture "foo" defined in f.spec.ts:5:30.');
|
||||
expect(result.output).toContain('f.spec.ts:9');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -317,7 +314,8 @@ test('should throw for unknown fixture parameter', async ({ runInlineTest }) =>
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('Fixture "foo" has unknown parameter "bar".');
|
||||
expect(result.output).toContain('f.spec.ts:5:29');
|
||||
expect(result.output).toContain('f.spec.ts:5');
|
||||
expect(stripAnsi(result.output)).toContain('const test = pwt.test.extend');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
@ -386,7 +384,7 @@ test('should error for unsupported scope', async ({ runInlineTest }) => {
|
||||
`
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain(`Error: Fixture "failure" has unknown { scope: 'foo' }`);
|
||||
expect(result.output).toContain(`Fixture "failure" has unknown { scope: 'foo' }`);
|
||||
});
|
||||
|
||||
test('should give enough time for fixture teardown', async ({ runInlineTest }) => {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test, expect } from './playwright-test-fixtures';
|
||||
import { test, expect, stripAnsi } from './playwright-test-fixtures';
|
||||
|
||||
test('should work', async ({ runInlineTest }) => {
|
||||
const { results } = await runInlineTest({
|
||||
@ -167,7 +167,8 @@ test('should fail if parameters are not destructured', async ({ runInlineTest })
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('First argument must use the object destructuring pattern: abc');
|
||||
expect(result.output).toContain('a.test.js:11:7');
|
||||
expect(result.output).toContain('a.test.js:11');
|
||||
expect(stripAnsi(result.output)).toContain('function (abc)');
|
||||
expect(result.results.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -180,7 +181,8 @@ test('should fail with an unknown fixture', async ({ runInlineTest }) => {
|
||||
`,
|
||||
});
|
||||
expect(result.output).toContain('Test has unknown parameter "asdf".');
|
||||
expect(result.output).toContain('a.test.js:5:11');
|
||||
expect(result.output).toContain('a.test.js:5');
|
||||
expect(stripAnsi(result.output)).toContain('async ({asdf})');
|
||||
expect(result.results.length).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
@ -27,7 +27,8 @@ test('test.describe.parallel should throw inside test.describe.serial', async ({
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain('a.test.ts:7:23: describe.parallel cannot be nested inside describe.serial');
|
||||
expect(result.output).toContain('Error: describe.parallel cannot be nested inside describe.serial');
|
||||
expect(result.output).toContain('a.test.ts:7');
|
||||
});
|
||||
|
||||
test('test.describe.parallel should work', async ({ runInlineTest }) => {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test, expect } from './playwright-test-fixtures';
|
||||
import { test, expect, stripAnsi } from './playwright-test-fixtures';
|
||||
|
||||
test('should merge options', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
@ -89,8 +89,8 @@ test('should throw when setting worker options in describe', async ({ runInlineT
|
||||
expect(result.output).toContain([
|
||||
`Cannot use({ foo }) in a describe group, because it forces a new worker.`,
|
||||
`Make it top-level in the test file or put in the configuration file.`,
|
||||
` "foo" defined at a.test.ts:9:14`,
|
||||
].join('\n'));
|
||||
expect(stripAnsi(result.output)).toContain(`{ foo: 'bar' }`);
|
||||
});
|
||||
|
||||
test('should run tests with different worker options', async ({ runInlineTest }) => {
|
||||
|
||||
@ -73,7 +73,7 @@ test('should respect test.setTimeout outside of the test', async ({ runInlineTes
|
||||
'a.spec.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test.setTimeout(500);
|
||||
test.setTimeout(1000);
|
||||
test('fails', async ({}) => {
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
@ -94,7 +94,7 @@ test('should respect test.setTimeout outside of the test', async ({ runInlineTes
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(2);
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.output).toContain('Test timeout of 500ms exceeded.');
|
||||
expect(result.output).toContain('Test timeout of 1000ms exceeded.');
|
||||
});
|
||||
|
||||
test('should timeout when calling test.setTimeout too late', async ({ runInlineTest }) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user