feat(test-runner): implement expect.poll (#12815)
This patch implements `expect.poll()` method that polls given
predicate until a given synchronous predicate completes.
Usage:
```js
// wait until page gets 3 frames.
await expect.poll(() => page.frames().length, {
timeout: 1000,
message: 'custom error message',
}).toBe(3);
```
Fixes #10235
This commit is contained in:
parent
009185bb89
commit
cfe92e1608
|
|
@ -96,6 +96,25 @@ The same works with soft assertions:
|
||||||
expect.soft(value, 'my soft assertion').toBe(56);
|
expect.soft(value, 'my soft assertion').toBe(56);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Polling
|
||||||
|
|
||||||
|
You can convert any synchronous `expect` to an asynchronous polling one using `expect.poll`.
|
||||||
|
|
||||||
|
The following method will poll given function until it returns HTTP status 200:
|
||||||
|
|
||||||
|
```js
|
||||||
|
expect.poll(async () => {
|
||||||
|
const response = await page.request.get('https://api.example.com');
|
||||||
|
return response.status();
|
||||||
|
}, {
|
||||||
|
// Custom error message
|
||||||
|
message: 'make sure API eventually succeeds', // custom error message
|
||||||
|
// Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
|
||||||
|
timeout: 10000,
|
||||||
|
}).toBe(200);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## API reference
|
## API reference
|
||||||
See the following pages for Playwright-specific assertions:
|
See the following pages for Playwright-specific assertions:
|
||||||
- [APIResponseAssertions] assertions for [APIResponse]
|
- [APIResponseAssertions] assertions for [APIResponse]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expectLibrary from 'expect';
|
import expectLibrary from 'expect';
|
||||||
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
INVERTED_COLOR,
|
INVERTED_COLOR,
|
||||||
|
|
@ -47,7 +48,8 @@ import { toMatchSnapshot, toHaveScreenshot, getSnapshotName } from './matchers/t
|
||||||
import type { Expect, TestError } from './types';
|
import type { Expect, TestError } from './types';
|
||||||
import matchers from 'expect/build/matchers';
|
import matchers from 'expect/build/matchers';
|
||||||
import { currentTestInfo } from './globals';
|
import { currentTestInfo } from './globals';
|
||||||
import { serializeError, captureStackTrace } from './util';
|
import { serializeError, captureStackTrace, currentExpectTimeout } from './util';
|
||||||
|
import { monotonicTime } from 'playwright-core/lib/utils/utils';
|
||||||
|
|
||||||
// #region
|
// #region
|
||||||
// Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts
|
// Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts
|
||||||
|
|
@ -89,21 +91,25 @@ export const printReceivedStringContainExpectedResult = (
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
function createExpect(actual: unknown, message: string|undefined, isSoft: boolean) {
|
type ExpectMessageOrOptions = undefined | string | { message?: string, timeout?: number };
|
||||||
if (message !== undefined && typeof message !== 'string')
|
|
||||||
throw new Error('expect(actual, optionalErrorMessage): optional error message must be a string.');
|
function createExpect(actual: unknown, messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean) {
|
||||||
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(message || '', isSoft));
|
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(messageOrOptions, isSoft, isPoll));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const expect: Expect = new Proxy(expectLibrary as any, {
|
export const expect: Expect = new Proxy(expectLibrary as any, {
|
||||||
apply: function(target: any, thisArg: any, argumentsList: [actual: unknown, message: string|undefined]) {
|
apply: function(target: any, thisArg: any, argumentsList: [actual: unknown, messageOrOptions: ExpectMessageOrOptions]) {
|
||||||
const [actual, message] = argumentsList;
|
const [actual, messageOrOptions] = argumentsList;
|
||||||
return createExpect(actual, message, false /* isSoft */);
|
return createExpect(actual, messageOrOptions, false /* isSoft */, false /* isPoll */);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect.soft = (actual: unknown, message: string|undefined) => {
|
expect.soft = (actual: unknown, messageOrOptions: ExpectMessageOrOptions) => {
|
||||||
return createExpect(actual, message, true /* isSoft */);
|
return createExpect(actual, messageOrOptions, true /* isSoft */, false /* isPoll */);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect.poll = (actual: unknown, messageOrOptions: ExpectMessageOrOptions) => {
|
||||||
|
return createExpect(actual, messageOrOptions, false /* isSoft */, true /* isPoll */);
|
||||||
};
|
};
|
||||||
|
|
||||||
expectLibrary.setState({ expand: false });
|
expectLibrary.setState({ expand: false });
|
||||||
|
|
@ -133,19 +139,25 @@ const customMatchers = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExpectMetaInfo = {
|
type ExpectMetaInfo = {
|
||||||
message: string;
|
message?: string;
|
||||||
isSoft: boolean;
|
isSoft: boolean;
|
||||||
|
isPoll: boolean;
|
||||||
|
pollTimeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let expectCallMetaInfo: undefined|ExpectMetaInfo = undefined;
|
let expectCallMetaInfo: undefined|ExpectMetaInfo = undefined;
|
||||||
|
|
||||||
class ExpectMetaInfoProxyHandler {
|
class ExpectMetaInfoProxyHandler {
|
||||||
private _message: string;
|
private _info: ExpectMetaInfo;
|
||||||
private _isSoft: boolean;
|
|
||||||
|
|
||||||
constructor(message: string, isSoft: boolean) {
|
constructor(messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean) {
|
||||||
this._message = message;
|
this._info = { isSoft, isPoll };
|
||||||
this._isSoft = isSoft;
|
if (typeof messageOrOptions === 'string') {
|
||||||
|
this._info.message = messageOrOptions;
|
||||||
|
} else {
|
||||||
|
this._info.message = messageOrOptions?.message;
|
||||||
|
this._info.pollTimeout = messageOrOptions?.timeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(target: any, prop: any, receiver: any): any {
|
get(target: any, prop: any, receiver: any): any {
|
||||||
|
|
@ -157,15 +169,17 @@ class ExpectMetaInfoProxyHandler {
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
return value.call(target, ...args);
|
return value.call(target, ...args);
|
||||||
const handleError = (e: Error) => {
|
const handleError = (e: Error) => {
|
||||||
if (this._isSoft)
|
if (this._info.isSoft)
|
||||||
testInfo._failWithError(serializeError(e), false /* isHardError */);
|
testInfo._failWithError(serializeError(e), false /* isHardError */);
|
||||||
else
|
else
|
||||||
throw e;
|
throw e;
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
expectCallMetaInfo = {
|
expectCallMetaInfo = {
|
||||||
message: this._message,
|
message: this._info.message,
|
||||||
isSoft: this._isSoft,
|
isSoft: this._info.isSoft,
|
||||||
|
isPoll: this._info.isPoll,
|
||||||
|
pollTimeout: this._info.pollTimeout,
|
||||||
};
|
};
|
||||||
let result = value.call(target, ...args);
|
let result = value.call(target, ...args);
|
||||||
if ((result instanceof Promise))
|
if ((result instanceof Promise))
|
||||||
|
|
@ -180,6 +194,36 @@ class ExpectMetaInfoProxyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pollMatcher(matcher: any, timeout: number, thisArg: any, generator: () => any, ...args: any[]) {
|
||||||
|
let result: { pass: boolean, message: () => string } | undefined = undefined;
|
||||||
|
const startTime = monotonicTime();
|
||||||
|
const pollIntervals = [100, 250, 500];
|
||||||
|
while (true) {
|
||||||
|
const elapsed = monotonicTime() - startTime;
|
||||||
|
if (timeout !== 0 && elapsed > timeout)
|
||||||
|
break;
|
||||||
|
const received = timeout !== 0 ? await raceAgainstTimeout(generator, timeout - elapsed) : await generator();
|
||||||
|
if (received.timedOut)
|
||||||
|
break;
|
||||||
|
result = matcher.call(thisArg, received.result, ...args);
|
||||||
|
const success = result!.pass !== thisArg.isNot;
|
||||||
|
if (success)
|
||||||
|
return result;
|
||||||
|
await new Promise(x => setTimeout(x, pollIntervals.shift() ?? 1000));
|
||||||
|
}
|
||||||
|
const timeoutMessage = `Timeout ${timeout}ms exceeded while waiting on the predicate`;
|
||||||
|
const message = result ? [
|
||||||
|
result.message(),
|
||||||
|
'',
|
||||||
|
`Call Log:`,
|
||||||
|
`- ${timeoutMessage}`,
|
||||||
|
].join('\n') : timeoutMessage;
|
||||||
|
return {
|
||||||
|
pass: thisArg.isNot,
|
||||||
|
message: () => message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function wrap(matcherName: string, matcher: any) {
|
function wrap(matcherName: string, matcher: any) {
|
||||||
return function(this: any, ...args: any[]) {
|
return function(this: any, ...args: any[]) {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
|
|
@ -196,10 +240,12 @@ function wrap(matcherName: string, matcher: any) {
|
||||||
const frame = stackTrace.frames[0];
|
const frame = stackTrace.frames[0];
|
||||||
const customMessage = expectCallMetaInfo?.message ?? '';
|
const customMessage = expectCallMetaInfo?.message ?? '';
|
||||||
const isSoft = expectCallMetaInfo?.isSoft ?? false;
|
const isSoft = expectCallMetaInfo?.isSoft ?? false;
|
||||||
|
const isPoll = expectCallMetaInfo?.isPoll ?? false;
|
||||||
|
const pollTimeout = expectCallMetaInfo?.pollTimeout;
|
||||||
const step = testInfo._addStep({
|
const step = testInfo._addStep({
|
||||||
location: frame && frame.file ? { file: path.resolve(process.cwd(), frame.file), line: frame.line || 0, column: frame.column || 0 } : undefined,
|
location: frame && frame.file ? { file: path.resolve(process.cwd(), frame.file), line: frame.line || 0, column: frame.column || 0 } : undefined,
|
||||||
category: 'expect',
|
category: 'expect',
|
||||||
title: customMessage || `expect${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}${titleSuffix}`,
|
title: customMessage || `expect${isPoll ? '.poll' : ''}${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}${titleSuffix}`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
forceNoParent: false
|
forceNoParent: false
|
||||||
});
|
});
|
||||||
|
|
@ -240,7 +286,19 @@ function wrap(matcherName: string, matcher: any) {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = matcher.call(this, ...args);
|
let result;
|
||||||
|
const [receivedOrGenerator, ...otherArgs] = args;
|
||||||
|
if (isPoll) {
|
||||||
|
if (typeof receivedOrGenerator !== 'function')
|
||||||
|
throw new Error('`expect.poll()` accepts only function as a first argument');
|
||||||
|
if ((customMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects')
|
||||||
|
throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
|
||||||
|
result = pollMatcher(matcher, currentExpectTimeout({ timeout: pollTimeout }), this, receivedOrGenerator, ...otherArgs);
|
||||||
|
} else {
|
||||||
|
if (typeof receivedOrGenerator === 'function')
|
||||||
|
throw new Error('Cannot accept function as a first argument; did you mean to use `expect.poll()`?');
|
||||||
|
result = matcher.call(this, ...args);
|
||||||
|
}
|
||||||
if (result instanceof Promise)
|
if (result instanceof Promise)
|
||||||
return result.then(reportStepEnd).catch(reportStepError);
|
return result.then(reportStepEnd).catch(reportStepError);
|
||||||
return reportStepEnd(result);
|
return reportStepEnd(result);
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,9 @@ type MakeMatchers<T, ReturnValue = T> = PlaywrightTest.Matchers<ReturnValue> &
|
||||||
ExtraMatchers<T, APIResponse, APIResponseMatchers>
|
ExtraMatchers<T, APIResponse, APIResponseMatchers>
|
||||||
|
|
||||||
export declare type Expect = {
|
export declare type Expect = {
|
||||||
<T = unknown>(actual: T, message?: string): MakeMatchers<T>;
|
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<T>;
|
||||||
soft: <T = unknown>(actual: T, message?: string) => MakeMatchers<T>;
|
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<T>;
|
||||||
|
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number }) => Omit<PlaywrightTest.Matchers<T>, 'rejects' | 'resolves'>;
|
||||||
|
|
||||||
extend(arg0: any): void;
|
extend(arg0: any): void;
|
||||||
getState(): expect.MatcherState;
|
getState(): expect.MatcherState;
|
||||||
|
|
|
||||||
146
tests/playwright-test/expect-poll.spec.ts
Normal file
146
tests/playwright-test/expect-poll.spec.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
/**
|
||||||
|
* Copyright Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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, stripAnsi } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test('should poll predicate', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should poll sync predicate', async () => {
|
||||||
|
let i = 0;
|
||||||
|
await test.expect.poll(() => ++i).toBe(3);
|
||||||
|
});
|
||||||
|
test('should poll async predicate', async () => {
|
||||||
|
let i = 0;
|
||||||
|
await test.expect.poll(async () => {
|
||||||
|
await new Promise(x => setTimeout(x, 50));
|
||||||
|
return ++i;
|
||||||
|
}).toBe(3);
|
||||||
|
});
|
||||||
|
test('should poll predicate that returns a promise', async () => {
|
||||||
|
let i = 0;
|
||||||
|
await test.expect.poll(() => Promise.resolve(++i)).toBe(3);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should compile', async ({ runTSC }) => {
|
||||||
|
const result = await runTSC({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should poll sync predicate', () => {
|
||||||
|
let i = 0;
|
||||||
|
test.expect.poll(() => ++i).toBe(3);
|
||||||
|
test.expect.poll(() => ++i, 'message').toBe(3);
|
||||||
|
test.expect.poll(() => ++i, { message: 'message' }).toBe(3);
|
||||||
|
test.expect.poll(() => ++i, { timeout: 100 }).toBe(3);
|
||||||
|
test.expect.poll(() => ++i, { message: 'message', timeout: 100 }).toBe(3);
|
||||||
|
test.expect.poll(async () => {
|
||||||
|
await new Promise(x => setTimeout(x, 50));
|
||||||
|
return ++i;
|
||||||
|
}).toBe(3);
|
||||||
|
test.expect.poll(() => Promise.resolve(++i)).toBe(3);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respect timeout', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async () => {
|
||||||
|
await test.expect.poll(() => false, { timeout: 100 }).toBe(3);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('Timeout 100ms exceeded while waiting on the predicate');
|
||||||
|
expect(stripAnsi(result.output)).toContain(`
|
||||||
|
7 | await test.expect.poll(() => false, { timeout: 100 }).
|
||||||
|
`.trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail when passed in non-function', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async () => {
|
||||||
|
await test.expect.poll(false).toBe(3);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('Error: `expect.poll()` accepts only function as a first argument');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail when used with web-first assertion', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async ({ page }) => {
|
||||||
|
await test.expect.poll(() => page.locator('body')).toHaveText('foo');
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('Error: `expect.poll()` does not support "toHaveText" matcher');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should time out when running infinite predicate', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async ({ page }) => {
|
||||||
|
await test.expect.poll(() => new Promise(x => {}), { timeout: 100 }).toBe(42);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('Timeout 100ms exceeded');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error that is thrown from predicate', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async ({ page }) => {
|
||||||
|
await test.expect.poll(() => { throw new Error('foo bar baz'); }, { timeout: 100 }).toBe(42);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('foo bar baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support .not predicate', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should fail', async ({ page }) => {
|
||||||
|
let i = 0;
|
||||||
|
await test.expect.poll(() => ++i).not.toBeLessThan(3);
|
||||||
|
expect(i).toBe(3);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
@ -23,6 +23,7 @@ test('soft expects should compile', async ({ runTSC }) => {
|
||||||
test('should work', () => {
|
test('should work', () => {
|
||||||
test.expect.soft(1+1).toBe(3);
|
test.expect.soft(1+1).toBe(3);
|
||||||
test.expect.soft(1+1, 'custom error message').toBe(3);
|
test.expect.soft(1+1, 'custom error message').toBe(3);
|
||||||
|
test.expect.soft(1+1, { message: 'custom error message' }).toBe(3);
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
@ -43,6 +44,19 @@ test('soft expects should work', async ({ runInlineTest }) => {
|
||||||
expect(stripAnsi(result.output)).toContain('woof-woof');
|
expect(stripAnsi(result.output)).toContain('woof-woof');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should fail when passed in function', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('should work', () => {
|
||||||
|
test.expect.soft(() => 1+1).toBe(2);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain('Cannot accept function as a first argument; did you mean to use `expect.poll()`?');
|
||||||
|
});
|
||||||
|
|
||||||
test('should report a mixture of soft and non-soft errors', async ({ runInlineTest }) => {
|
test('should report a mixture of soft and non-soft errors', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.spec.ts': `
|
'a.spec.ts': `
|
||||||
|
|
@ -51,7 +65,7 @@ test('should report a mixture of soft and non-soft errors', async ({ runInlineTe
|
||||||
test.expect.soft(1+1, 'one plus one').toBe(3);
|
test.expect.soft(1+1, 'one plus one').toBe(3);
|
||||||
test.expect.soft(2*2, 'two times two').toBe(5);
|
test.expect.soft(2*2, 'two times two').toBe(5);
|
||||||
test.expect(3/3, 'three div three').toBe(7);
|
test.expect(3/3, 'three div three').toBe(7);
|
||||||
test.expect.soft(6-4, 'six minus four').toBe(3);
|
test.expect.soft(6-4, { message: 'six minus four' }).toBe(3);
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -67,20 +67,6 @@ test('should not expand huge arrays', async ({ runInlineTest }) => {
|
||||||
expect(result.output.length).toBeLessThan(100000);
|
expect(result.output.length).toBeLessThan(100000);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should fail when passed `null` instead of message', async ({ runInlineTest }) => {
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'expect-test.spec.ts': `
|
|
||||||
const { test } = pwt;
|
|
||||||
test('custom expect message', () => {
|
|
||||||
test.expect(1+1, null).toEqual(3);
|
|
||||||
});
|
|
||||||
`
|
|
||||||
});
|
|
||||||
expect(result.exitCode).toBe(1);
|
|
||||||
expect(result.passed).toBe(0);
|
|
||||||
expect(stripAnsi(result.output)).toContain(`optional error message must be a string.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should include custom error message', async ({ runInlineTest }) => {
|
test('should include custom error message', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'expect-test.spec.ts': `
|
'expect-test.spec.ts': `
|
||||||
|
|
@ -105,7 +91,7 @@ test('should include custom error message with web-first assertions', async ({ r
|
||||||
'expect-test.spec.ts': `
|
'expect-test.spec.ts': `
|
||||||
const { test } = pwt;
|
const { test } = pwt;
|
||||||
test('custom expect message', async ({page}) => {
|
test('custom expect message', async ({page}) => {
|
||||||
await expect(page.locator('x-foo'), 'x-foo must be visible').toBeVisible({timeout: 1});
|
await expect(page.locator('x-foo'), { message: 'x-foo must be visible' }).toBeVisible({timeout: 1});
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue