chore: report more fatal errors via reporter (#19640)

This commit is contained in:
Pavel Feldman 2022-12-22 17:31:02 -08:00 committed by GitHub
parent 8b80e22a03
commit 233664bd30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 120 additions and 88 deletions

View File

@ -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);
}

View File

@ -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}`);
}

View File

@ -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' };
}

View File

@ -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,

View File

@ -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();

View File

@ -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 }) => {

View File

@ -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);
});

View File

@ -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 }) => {

View File

@ -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 }) => {

View File

@ -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 }) => {