diff --git a/packages/playwright-test/src/fixtures.ts b/packages/playwright-test/src/fixtures.ts index e3c546b2d5..0fa43c3900 100644 --- a/packages/playwright-test/src/fixtures.ts +++ b/packages/playwright-test/src/fixtures.ts @@ -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); -} diff --git a/packages/playwright-test/src/globals.ts b/packages/playwright-test/src/globals.ts index fb5119d613..a14adbac1d 100644 --- a/packages/playwright-test/src/globals.ts +++ b/packages/playwright-test/src/globals.ts @@ -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}`); +} diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index eee7bcec49..dce8918f33 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -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 { 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' }; } diff --git a/packages/playwright-test/src/testType.ts b/packages/playwright-test/src/testType.ts index 1d91ed30cc..245d3f3147 100644 --- a/packages/playwright-test/src/testType.ts +++ b/packages/playwright-test/src/testType.ts @@ -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(location: Location, title: string, body: () => Promise): Promise { 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, diff --git a/packages/playwright-test/src/util.ts b/packages/playwright-test/src/util.ts index 6a2dae38c8..ff4aac5370 100644 --- a/packages/playwright-test/src/util.ts +++ b/packages/playwright-test/src/util.ts @@ -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(); diff --git a/tests/playwright-test/fixture-errors.spec.ts b/tests/playwright-test/fixture-errors.spec.ts index adbe892718..60e7290a88 100644 --- a/tests/playwright-test/fixture-errors.spec.ts +++ b/tests/playwright-test/fixture-errors.spec.ts @@ -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 }) => { diff --git a/tests/playwright-test/fixtures.spec.ts b/tests/playwright-test/fixtures.spec.ts index 888d71044b..34938d0c9c 100644 --- a/tests/playwright-test/fixtures.spec.ts +++ b/tests/playwright-test/fixtures.spec.ts @@ -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); }); diff --git a/tests/playwright-test/test-parallel.spec.ts b/tests/playwright-test/test-parallel.spec.ts index c76a532de1..e7f5a1d26c 100644 --- a/tests/playwright-test/test-parallel.spec.ts +++ b/tests/playwright-test/test-parallel.spec.ts @@ -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 }) => { diff --git a/tests/playwright-test/test-use.spec.ts b/tests/playwright-test/test-use.spec.ts index 9a058c3b7b..e7be1b7d87 100644 --- a/tests/playwright-test/test-use.spec.ts +++ b/tests/playwright-test/test-use.spec.ts @@ -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 }) => { diff --git a/tests/playwright-test/timeout.spec.ts b/tests/playwright-test/timeout.spec.ts index 8faba8ab10..02ad1c1f96 100644 --- a/tests/playwright-test/timeout.spec.ts +++ b/tests/playwright-test/timeout.spec.ts @@ -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 }) => {