cherry-pick(#35637): chore: completely remove annotation warnings code
This commit is contained in:
parent
a0fdb3fb99
commit
0259acf7b3
|
@ -25,8 +25,6 @@ import { wrapFunctionWithLocation } from '../transform/transform';
|
|||
import type { FixturesWithLocation } from './config';
|
||||
import type { Fixtures, TestDetails, TestStepInfo, TestType } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { TestInfoImpl, TestStepInternal } from '../worker/testInfo';
|
||||
|
||||
|
||||
const testTypeSymbol = Symbol('testType');
|
||||
|
||||
|
@ -262,15 +260,11 @@ export class TestTypeImpl {
|
|||
suite._use.push({ fixtures, location });
|
||||
}
|
||||
|
||||
_step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
async _step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw new Error(`test.step() can only be called from a test`);
|
||||
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
||||
return testInfo._floatingPromiseScope.wrapPromiseAPIResult(this._stepInternal(expectation, testInfo, step, body, options), step.location);
|
||||
}
|
||||
|
||||
private async _stepInternal<T>(expectation: 'pass'|'skip', testInfo: TestInfoImpl, step: TestStepInternal, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
return await currentZone().with('stepZone', step).run(async () => {
|
||||
try {
|
||||
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
|
||||
|
|
|
@ -385,10 +385,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||
setMatcherCallContext({ expectInfo: this._info, testInfo, step: step.info });
|
||||
const callback = () => matcher.call(target, ...args);
|
||||
const result = currentZone().with('stepZone', step).run(callback);
|
||||
if (result instanceof Promise) {
|
||||
const promise = result.then(finalizer).catch(reportStepError);
|
||||
return testInfo._floatingPromiseScope.wrapPromiseAPIResult(promise, stackFrames[0]);
|
||||
}
|
||||
if (result instanceof Promise)
|
||||
return result.then(finalizer).catch(reportStepError);
|
||||
finalizer();
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
|
|
@ -319,7 +319,6 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
|
|||
const header = formatTestHeader(screen, config, test, { indent: ' ', index, mode: 'error' });
|
||||
lines.push(screen.colors.red(header));
|
||||
for (const result of test.results) {
|
||||
// const warnings = result.annotations.filter(a => a.type === 'warning');
|
||||
const resultLines: string[] = [];
|
||||
const errors = formatResultFailure(screen, test, result, ' ');
|
||||
if (!errors.length)
|
||||
|
@ -329,11 +328,6 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
|
|||
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
resultLines.push(...errors.map(error => '\n' + error.message));
|
||||
// TODO: 1.53: Actually build annotations
|
||||
// if (warnings.length) {
|
||||
// resultLines.push('');
|
||||
// resultLines.push(...formatTestWarning(screen, config, warnings));
|
||||
// }
|
||||
for (let i = 0; i < result.attachments.length; ++i) {
|
||||
const attachment = result.attachments[i];
|
||||
if (attachment.name.startsWith('_error-context') && attachment.path) {
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Location } from '../../types/test';
|
||||
|
||||
export class FloatingPromiseScope {
|
||||
readonly _floatingCalls: Map<Promise<any>, Location | undefined> = new Map();
|
||||
|
||||
/**
|
||||
* Enables a promise API call to be tracked by the test, alerting if unawaited.
|
||||
*
|
||||
* **NOTE:** Returning from an async function wraps the result in a promise, regardless of whether the return value is a promise. This will automatically mark the promise as awaited. Avoid this.
|
||||
*/
|
||||
wrapPromiseAPIResult<T>(promise: Promise<T>, location: Location | undefined): Promise<T> {
|
||||
if (process.env.PW_DISABLE_FLOATING_PROMISES_WARNING)
|
||||
return promise;
|
||||
|
||||
const promiseProxy = new Proxy(promise, {
|
||||
get: (target, prop, receiver) => {
|
||||
if (prop === 'then') {
|
||||
return (...args: any[]) => {
|
||||
this._floatingCalls.delete(promise);
|
||||
|
||||
const originalThen = Reflect.get(target, prop, receiver) as Promise<T>['then'];
|
||||
return originalThen.call(target, ...args);
|
||||
};
|
||||
} else {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._floatingCalls.set(promise, location);
|
||||
|
||||
return promiseProxy;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._floatingCalls.clear();
|
||||
}
|
||||
|
||||
hasFloatingPromises(): boolean {
|
||||
return this._floatingCalls.size > 0;
|
||||
}
|
||||
|
||||
floatingPromises(): Array<{ location: Location | undefined, promise: Promise<any> }> {
|
||||
return Array.from(this._floatingCalls.entries()).map(([promise, location]) => ({ location, promise }));
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutMana
|
|||
import { filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util';
|
||||
import { TestTracing } from './testTracing';
|
||||
import { testInfoError } from './util';
|
||||
import { FloatingPromiseScope } from './floatingPromiseScope';
|
||||
|
||||
import type { RunnableDescription } from './timeoutManager';
|
||||
import type { FullProject, TestInfo, TestStatus, TestStepInfo } from '../../types/test';
|
||||
|
@ -60,7 +59,6 @@ export class TestInfoImpl implements TestInfo {
|
|||
readonly _startTime: number;
|
||||
readonly _startWallTime: number;
|
||||
readonly _tracing: TestTracing;
|
||||
readonly _floatingPromiseScope: FloatingPromiseScope = new FloatingPromiseScope();
|
||||
readonly _uniqueSymbol;
|
||||
|
||||
_wasInterrupted = false;
|
||||
|
|
|
@ -427,26 +427,9 @@ export class WorkerMain extends ProcessRunner {
|
|||
throw firstAfterHooksError;
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
if (testInfo._isFailure()) {
|
||||
if (testInfo._isFailure())
|
||||
this._isStopped = true;
|
||||
|
||||
// Only if failed, create warning if any of the async calls were not awaited in various stages.
|
||||
if (!process.env.PW_DISABLE_FLOATING_PROMISES_WARNING && testInfo._floatingPromiseScope.hasFloatingPromises()) {
|
||||
// TODO: 1.53: Actually build annotations
|
||||
// Dedupe by location
|
||||
// const annotationLocations = new Map<string | undefined, Location | undefined>(testInfo._floatingPromiseScope.floatingPromises().map(
|
||||
// ({ location }) => {
|
||||
// const locationKey = location ? `${location.file}:${location.line}:${location.column}` : undefined;
|
||||
// return [locationKey, location];
|
||||
// }));
|
||||
|
||||
// testInfo.annotations.push(...[...annotationLocations.values()].map(location => ({
|
||||
// type: 'warning', description: `This async call was not awaited by the end of the test. This can cause flakiness. It is recommended to run ESLint with "@typescript-eslint/no-floating-promises" to verify.`, location
|
||||
// })));
|
||||
testInfo._floatingPromiseScope.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isStopped) {
|
||||
// Run all remaining "afterAll" hooks and teardown all fixtures when worker is shutting down.
|
||||
// Mark as "cleaned up" early to avoid running cleanup twice.
|
||||
|
|
|
@ -486,72 +486,5 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
expect(text).toContain('› passes @bar1 @bar2 (');
|
||||
expect(text).toContain('› passes @baz1 @baz2 (');
|
||||
});
|
||||
|
||||
test.skip('should show warnings on failing tests', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('fail', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
throw new Error();
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain('Warning: a.spec.ts:5:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: foo');
|
||||
});
|
||||
|
||||
test.skip('should not show warnings on passing tests', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('success', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).not.toContain('Warning: foo');
|
||||
});
|
||||
|
||||
test.skip('should properly sort warnings', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'external.js': `
|
||||
import { expect } from '@playwright/test';
|
||||
export const externalAsyncCall = (page) => {
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
};
|
||||
`,
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { externalAsyncCall } from './external.js';
|
||||
test('fail a', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
externalAsyncCall(page);
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
testInfo.annotations.push({ type: 'warning', description: 'bar' });
|
||||
throw new Error();
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain('Warning: a.spec.ts:7:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: external.js:4:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: foo');
|
||||
expect(result.output).toContain('Warning: bar');
|
||||
|
||||
const manualIndexFoo = result.output.indexOf('Warning: foo');
|
||||
const manualIndexBar = result.output.indexOf('Warning: bar');
|
||||
const externalIndex = result.output.indexOf('Warning: external.js:4:41');
|
||||
const specIndex = result.output.indexOf('Warning: a.spec.ts:7:41');
|
||||
expect(specIndex).toBeLessThan(externalIndex);
|
||||
expect(externalIndex).toBeLessThan(manualIndexFoo);
|
||||
expect(manualIndexFoo).toBeLessThan(manualIndexBar);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test, expect } from './playwright-test-fixtures';
|
||||
|
||||
const description = 'This async call was not awaited by the end of the test. This can cause flakiness. It is recommended to run ESLint with "@typescript-eslint/no-floating-promises" to verify.';
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
test.describe.skip('await', () => {
|
||||
test('should not care about non-API promises', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test } from '@playwright/test';
|
||||
test('test', async () => {
|
||||
new Promise(() => {});
|
||||
await expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should warn on failure', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('custom test name', async ({ page }) => {
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
// Timeout to make sure the expect actually gets processed
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 39 }) }]);
|
||||
});
|
||||
|
||||
test('should not warn on success', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('custom test name', async ({ page }) => {
|
||||
await page.setContent('<div>A</div>');
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(results[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should warn about missing await on expects', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('custom test name', async ({ page }) => {
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 39 }) }]);
|
||||
});
|
||||
|
||||
test('should not warn when not missing await on expects', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
await expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should not warn when using then() on expects', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
expect(page.locator('div')).toHaveText('A').then(() => {});
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should warn about missing await on resolve', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
expect(Promise.reject(new Error('foo'))).resolves.toBe('foo');
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 61 }) }]);
|
||||
});
|
||||
|
||||
test('should warn about missing await on reject.not', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
expect(Promise.reject(new Error('foo'))).rejects.not.toThrow('foo');
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 64 }) }]);
|
||||
});
|
||||
|
||||
test('should warn about missing await on test.step', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
test.step('step', () => {});
|
||||
await expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 16 }) }]);
|
||||
});
|
||||
|
||||
test('should not warn when not missing await on test.step', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
await test.step('step', () => {});
|
||||
await expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should warn about missing await on test.step.skip', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
test.step.skip('step', () => {});
|
||||
await expect(page.locator('div')).toHaveText('A');
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 21 }) }]);
|
||||
});
|
||||
|
||||
test('traced promise should be instanceof Promise', async ({ runInlineTest }) => {
|
||||
const { exitCode } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
await page.setContent('<div>A</div>');
|
||||
const expectPromise = expect(page.locator('div')).toHaveText('A');
|
||||
expect(expectPromise instanceof Promise).toBeTruthy();
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should warn about missing await in before hooks', async ({ runInlineTest }) => {
|
||||
const group = ['beforeAll', 'beforeEach'];
|
||||
for (const hook of group) {
|
||||
await test.step(hook, async () => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
let page;
|
||||
test.${hook}(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
await page.setContent('<div>A</div>');
|
||||
expect(page.locator('div')).toHaveText('A');
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
test('test ${hook}', async () => {
|
||||
await expect(page.locator('button')).toBeVisible({ timeout: 100 });
|
||||
});
|
||||
`
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 7, column: 43 }) }]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('should warn about missing await in after hooks', () => {
|
||||
const group = ['afterAll', 'afterEach'];
|
||||
for (const hook of group) {
|
||||
test(hook, async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
let page;
|
||||
test('test ${hook}', async ({ browser }) => {
|
||||
await expect(Promise.resolve()).resolves.toBe('A');
|
||||
});
|
||||
test.${hook}(async () => {
|
||||
expect(Promise.resolve()).resolves.toBe(undefined);
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 8, column: 50 }) }]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('should warn about missing await across hooks and test', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test.beforeAll(async () => {
|
||||
expect(Promise.resolve()).resolves.toBe(undefined);
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
test('test', async () => {
|
||||
expect(Promise.resolve()).resolves.toBe('A');
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
test.afterEach(async () => {
|
||||
expect(Promise.resolve()).resolves.toBe(undefined);
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(exitCode).toBe(1);
|
||||
expect(results[0].annotations).toEqual([
|
||||
{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 4, column: 46 }) },
|
||||
{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 8, column: 46 }) },
|
||||
{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 12, column: 46 }) },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should dedupe warnings that occur at the same location', async ({ runInlineTest }) => {
|
||||
const { exitCode, results } = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ page }) => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
}
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
});
|
||||
`
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
|
||||
expect(results[0].annotations).toEqual([
|
||||
{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 5, column: 41 }) },
|
||||
{ type: 'warning', description, location: expect.objectContaining({ file: expect.stringMatching(/a\.test\.ts$/), line: 7, column: 39 }) },
|
||||
]);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue