chore: populate matcherResult in web assertions (#27133)
Ref https://github.com/microsoft/playwright/issues/26929
This commit is contained in:
Родитель
3bcf9687e6
Коммит
9fe037fb63
|
@ -18,7 +18,7 @@ import type * as structs from '../../types/structs';
|
|||
import type * as api from '../../types/types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import * as util from 'util';
|
||||
import { isString, monotonicTime } from '../utils';
|
||||
import { asLocator, isString, monotonicTime } from '../utils';
|
||||
import { ElementHandle } from './elementHandle';
|
||||
import type { Frame } from './frame';
|
||||
import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
||||
|
@ -355,7 +355,7 @@ export class Locator implements api.Locator {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return `Locator@${this._selector}`;
|
||||
return asLocator('javascript', this._selector, undefined, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -265,16 +265,6 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||
const message = jestError.message;
|
||||
if (customMessage) {
|
||||
const messageLines = message.split('\n');
|
||||
// Jest adds something like the following error to all errors:
|
||||
// expect(received).toBe(expected); // Object.is equality
|
||||
const uselessMatcherLineIndex = messageLines.findIndex((line: string) => /expect.*\(.*received.*\)/.test(line));
|
||||
if (uselessMatcherLineIndex !== -1) {
|
||||
// if there's a newline after the matcher text, then remove it as well.
|
||||
if (uselessMatcherLineIndex + 1 < messageLines.length && messageLines[uselessMatcherLineIndex + 1].trim() === '')
|
||||
messageLines.splice(uselessMatcherLineIndex, 2);
|
||||
else
|
||||
messageLines.splice(uselessMatcherLineIndex, 1);
|
||||
}
|
||||
const newMessage = [
|
||||
customMessage,
|
||||
'',
|
||||
|
|
|
@ -16,10 +16,22 @@
|
|||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import type { ExpectMatcherContext } from './expect';
|
||||
import type { Locator } from 'playwright-core';
|
||||
|
||||
export function matcherHint(state: ExpectMatcherContext, matcherName: string, a: any, b: any, matcherOptions: any, timeout?: number) {
|
||||
const message = state.utils.matcherHint(matcherName, a, b, matcherOptions);
|
||||
export function matcherHint(state: ExpectMatcherContext, locator: Locator | undefined, matcherName: string, expression: any, actual: any, matcherOptions: any, timeout?: number) {
|
||||
let header = state.utils.matcherHint(matcherName, expression, actual, matcherOptions).replace(/ \/\/ deep equality/, '') + '\n\n';
|
||||
if (timeout)
|
||||
return colors.red(`Timed out ${timeout}ms waiting for `) + message;
|
||||
return message;
|
||||
header = colors.red(`Timed out ${timeout}ms waiting for `) + header;
|
||||
if (locator)
|
||||
header += `Locator: ${locator}\n`;
|
||||
return header;
|
||||
}
|
||||
|
||||
export type MatcherResult<E, A> = {
|
||||
locator: Locator;
|
||||
name: string;
|
||||
expected: E;
|
||||
message: () => string;
|
||||
pass: boolean;
|
||||
actual?: A;
|
||||
};
|
||||
|
|
|
@ -39,8 +39,11 @@ export function toBeAttached(
|
|||
locator: LocatorEx,
|
||||
options?: { attached?: boolean, timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeAttached', locator, 'Locator', async (isNot, timeout) => {
|
||||
const attached = !options || options.attached === undefined || options.attached === true;
|
||||
const attached = !options || options.attached === undefined || options.attached === true;
|
||||
const expected = attached ? 'attached' : 'detached';
|
||||
const unexpected = attached ? 'detached' : 'attached';
|
||||
const arg = attached ? '' : '{ attached: false }';
|
||||
return toBeTruthy.call(this, 'toBeAttached', locator, 'Locator', expected, unexpected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(attached ? 'to.be.attached' : 'to.be.detached', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -50,8 +53,11 @@ export function toBeChecked(
|
|||
locator: LocatorEx,
|
||||
options?: { checked?: boolean, timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', async (isNot, timeout) => {
|
||||
const checked = !options || options.checked === undefined || options.checked === true;
|
||||
const checked = !options || options.checked === undefined || options.checked === true;
|
||||
const expected = checked ? 'checked' : 'unchecked';
|
||||
const unexpected = checked ? 'unchecked' : 'checked';
|
||||
const arg = checked ? '' : '{ checked: false }';
|
||||
return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', expected, unexpected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(checked ? 'to.be.checked' : 'to.be.unchecked', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -61,7 +67,7 @@ export function toBeDisabled(
|
|||
locator: LocatorEx,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeDisabled', locator, 'Locator', async (isNot, timeout) => {
|
||||
return toBeTruthy.call(this, 'toBeDisabled', locator, 'Locator', 'disabled', 'enabled', '', async (isNot, timeout) => {
|
||||
return await locator._expect('to.be.disabled', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -71,8 +77,11 @@ export function toBeEditable(
|
|||
locator: LocatorEx,
|
||||
options?: { editable?: boolean, timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeEditable', locator, 'Locator', async (isNot, timeout) => {
|
||||
const editable = !options || options.editable === undefined || options.editable === true;
|
||||
const editable = !options || options.editable === undefined || options.editable === true;
|
||||
const expected = editable ? 'editable' : 'readOnly';
|
||||
const unexpected = editable ? 'readOnly' : 'editable';
|
||||
const arg = editable ? '' : '{ editable: false }';
|
||||
return toBeTruthy.call(this, 'toBeEditable', locator, 'Locator', expected, unexpected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(editable ? 'to.be.editable' : 'to.be.readonly', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -82,7 +91,7 @@ export function toBeEmpty(
|
|||
locator: LocatorEx,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeEmpty', locator, 'Locator', async (isNot, timeout) => {
|
||||
return toBeTruthy.call(this, 'toBeEmpty', locator, 'Locator', 'empty', 'notEmpty', '', async (isNot, timeout) => {
|
||||
return await locator._expect('to.be.empty', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -92,8 +101,11 @@ export function toBeEnabled(
|
|||
locator: LocatorEx,
|
||||
options?: { enabled?: boolean, timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeEnabled', locator, 'Locator', async (isNot, timeout) => {
|
||||
const enabled = !options || options.enabled === undefined || options.enabled === true;
|
||||
const enabled = !options || options.enabled === undefined || options.enabled === true;
|
||||
const expected = enabled ? 'enabled' : 'disabled';
|
||||
const unexpected = enabled ? 'disabled' : 'enabled';
|
||||
const arg = enabled ? '' : '{ enabled: false }';
|
||||
return toBeTruthy.call(this, 'toBeEnabled', locator, 'Locator', expected, unexpected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(enabled ? 'to.be.enabled' : 'to.be.disabled', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -103,7 +115,7 @@ export function toBeFocused(
|
|||
locator: LocatorEx,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeFocused', locator, 'Locator', async (isNot, timeout) => {
|
||||
return toBeTruthy.call(this, 'toBeFocused', locator, 'Locator', 'focused', 'inactive', '', async (isNot, timeout) => {
|
||||
return await locator._expect('to.be.focused', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -113,7 +125,7 @@ export function toBeHidden(
|
|||
locator: LocatorEx,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeHidden', locator, 'Locator', async (isNot, timeout) => {
|
||||
return toBeTruthy.call(this, 'toBeHidden', locator, 'Locator', 'hidden', 'visible', '', async (isNot, timeout) => {
|
||||
return await locator._expect('to.be.hidden', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -123,8 +135,11 @@ export function toBeVisible(
|
|||
locator: LocatorEx,
|
||||
options?: { visible?: boolean, timeout?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeVisible', locator, 'Locator', async (isNot, timeout) => {
|
||||
const visible = !options || options.visible === undefined || options.visible === true;
|
||||
const visible = !options || options.visible === undefined || options.visible === true;
|
||||
const expected = visible ? 'visible' : 'hidden';
|
||||
const unexpected = visible ? 'hidden' : 'visible';
|
||||
const arg = visible ? '' : '{ visible: false }';
|
||||
return toBeTruthy.call(this, 'toBeVisible', locator, 'Locator', expected, unexpected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(visible ? 'to.be.visible' : 'to.be.hidden', { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
@ -134,7 +149,7 @@ export function toBeInViewport(
|
|||
locator: LocatorEx,
|
||||
options?: { timeout?: number, ratio?: number },
|
||||
) {
|
||||
return toBeTruthy.call(this, 'toBeInViewport', locator, 'Locator', async (isNot, timeout) => {
|
||||
return toBeTruthy.call(this, 'toBeInViewport', locator, 'Locator', 'in viewport', 'outside viewport', '', async (isNot, timeout) => {
|
||||
return await locator._expect('to.be.in.viewport', { isNot, expectedNumber: options?.ratio, timeout });
|
||||
}, options);
|
||||
}
|
||||
|
|
|
@ -16,17 +16,22 @@
|
|||
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
import { matcherHint } from './matcherHint';
|
||||
import type { MatcherResult } from './matcherHint';
|
||||
import { currentExpectTimeout } from '../common/globals';
|
||||
import type { ExpectMatcherContext } from './expect';
|
||||
import type { Locator } from 'playwright-core';
|
||||
|
||||
export async function toBeTruthy(
|
||||
this: ExpectMatcherContext,
|
||||
matcherName: string,
|
||||
receiver: any,
|
||||
receiver: Locator,
|
||||
receiverType: string,
|
||||
expected: string,
|
||||
unexpected: string,
|
||||
arg: string,
|
||||
query: (isNot: boolean, timeout: number) => Promise<{ matches: boolean, log?: string[], received?: any, timedOut?: boolean }>,
|
||||
options: { timeout?: number } = {},
|
||||
) {
|
||||
): Promise<MatcherResult<any, any>> {
|
||||
expectTypes(receiver, [receiverType], matcherName);
|
||||
|
||||
const matcherOptions = {
|
||||
|
@ -35,12 +40,13 @@ export async function toBeTruthy(
|
|||
};
|
||||
|
||||
const timeout = currentExpectTimeout(options);
|
||||
|
||||
const { matches, log, timedOut } = await query(!!this.isNot, timeout);
|
||||
|
||||
const actual = matches ? expected : unexpected;
|
||||
const message = () => {
|
||||
return matcherHint(this, matcherName, undefined, '', matcherOptions, timedOut ? timeout : undefined) + callLogText(log);
|
||||
const header = matcherHint(this, receiver, matcherName, 'locator', arg, matcherOptions, timedOut ? timeout : undefined);
|
||||
const logText = callLogText(log);
|
||||
return matches ? `${header}Expected: not ${expected}\nReceived: ${expected}${logText}` :
|
||||
`${header}Expected: ${expected}\nReceived: ${unexpected}${logText}`;
|
||||
};
|
||||
|
||||
return { message, pass: matches };
|
||||
return { locator: receiver, message, pass: matches, actual, name: matcherName, expected };
|
||||
}
|
||||
|
|
|
@ -14,11 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { expectTypes } from '../util';
|
||||
import { callLogText } from '../util';
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
import { matcherHint } from './matcherHint';
|
||||
import type { MatcherResult } from './matcherHint';
|
||||
import { currentExpectTimeout } from '../common/globals';
|
||||
import type { ExpectMatcherContext } from './expect';
|
||||
import type { Locator } from 'playwright-core';
|
||||
|
||||
// Omit colon and one or more spaces, so can call getLabelPrinter.
|
||||
const EXPECTED_LABEL = 'Expected';
|
||||
|
@ -30,12 +31,12 @@ const isExpand = (expand?: boolean): boolean => expand !== false;
|
|||
export async function toEqual<T>(
|
||||
this: ExpectMatcherContext,
|
||||
matcherName: string,
|
||||
receiver: any,
|
||||
receiver: Locator,
|
||||
receiverType: string,
|
||||
query: (isNot: boolean, timeout: number) => Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }>,
|
||||
expected: T,
|
||||
options: { timeout?: number, contains?: boolean } = {},
|
||||
) {
|
||||
): Promise<MatcherResult<any, any>> {
|
||||
expectTypes(receiver, [receiverType], matcherName);
|
||||
|
||||
const matcherOptions = {
|
||||
|
@ -50,15 +51,11 @@ export async function toEqual<T>(
|
|||
|
||||
const message = pass
|
||||
? () =>
|
||||
matcherHint(this, matcherName, undefined, undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
'\n\n' +
|
||||
matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
`Expected: not ${this.utils.printExpected(expected)}\n` +
|
||||
(this.utils.stringify(expected) !== this.utils.stringify(received)
|
||||
? `Received: ${this.utils.printReceived(received)}`
|
||||
: '') + callLogText(log)
|
||||
`Received: ${this.utils.printReceived(received)}` + callLogText(log)
|
||||
: () =>
|
||||
matcherHint(this, matcherName, undefined, undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
'\n\n' +
|
||||
matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
this.utils.printDiffOrStringify(
|
||||
expected,
|
||||
received,
|
||||
|
@ -70,5 +67,5 @@ export async function toEqual<T>(
|
|||
// Passing the actual and expected objects so that a custom reporter
|
||||
// could access them, for example in order to display a custom visual diff,
|
||||
// or create a different error message
|
||||
return { actual: received, expected, message, name: matcherName, pass };
|
||||
return { locator: receiver, actual: received, expected, message, name: matcherName, pass };
|
||||
}
|
||||
|
|
|
@ -24,17 +24,19 @@ import {
|
|||
printReceivedStringContainExpectedSubstring
|
||||
} from './expect';
|
||||
import { matcherHint } from './matcherHint';
|
||||
import type { MatcherResult } from './matcherHint';
|
||||
import { currentExpectTimeout } from '../common/globals';
|
||||
import type { Locator } from 'playwright-core';
|
||||
|
||||
export async function toMatchText(
|
||||
this: ExpectMatcherContext,
|
||||
matcherName: string,
|
||||
receiver: any,
|
||||
receiver: Locator,
|
||||
receiverType: string,
|
||||
query: (isNot: boolean, timeout: number) => Promise<{ matches: boolean, received?: string, log?: string[], timedOut?: boolean }>,
|
||||
expected: string | RegExp,
|
||||
options: { timeout?: number, matchSubstring?: boolean } = {},
|
||||
) {
|
||||
): Promise<MatcherResult<string | RegExp, string>> {
|
||||
expectTypes(receiver, [receiverType], matcherName);
|
||||
|
||||
const matcherOptions = {
|
||||
|
@ -48,7 +50,7 @@ export async function toMatchText(
|
|||
) {
|
||||
throw new Error(
|
||||
this.utils.matcherErrorMessage(
|
||||
matcherHint(this, matcherName, undefined, undefined, matcherOptions),
|
||||
matcherHint(this, receiver, matcherName, receiver, expected, matcherOptions),
|
||||
`${this.utils.EXPECTED_COLOR(
|
||||
'expected',
|
||||
)} value must be a string or regular expression`,
|
||||
|
@ -65,16 +67,14 @@ export async function toMatchText(
|
|||
const message = pass
|
||||
? () =>
|
||||
typeof expected === 'string'
|
||||
? matcherHint(this, matcherName, undefined, undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
'\n\n' +
|
||||
? matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
`Expected ${stringSubstring}: not ${this.utils.printExpected(expected)}\n` +
|
||||
`Received string: ${printReceivedStringContainExpectedSubstring(
|
||||
receivedString,
|
||||
receivedString.indexOf(expected),
|
||||
expected.length,
|
||||
)}` + callLogText(log)
|
||||
: matcherHint(this, matcherName, undefined, undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
'\n\n' +
|
||||
: matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
`Expected pattern: not ${this.utils.printExpected(expected)}\n` +
|
||||
`Received string: ${printReceivedStringContainExpectedResult(
|
||||
receivedString,
|
||||
|
@ -88,8 +88,7 @@ export async function toMatchText(
|
|||
const labelReceived = 'Received string';
|
||||
|
||||
return (
|
||||
matcherHint(this, matcherName, undefined, undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
'\n\n' +
|
||||
matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined) +
|
||||
this.utils.printDiffOrStringify(
|
||||
expected,
|
||||
receivedString,
|
||||
|
@ -99,7 +98,14 @@ export async function toMatchText(
|
|||
)) + callLogText(log);
|
||||
};
|
||||
|
||||
return { message, pass };
|
||||
return {
|
||||
locator: receiver,
|
||||
name: matcherName,
|
||||
expected,
|
||||
message,
|
||||
pass,
|
||||
actual: received,
|
||||
};
|
||||
}
|
||||
|
||||
export function toExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean, ignoreCase?: boolean } = {}): ExpectedTextValue[] {
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* 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 { stripAnsi } from '../config/utils';
|
||||
import { test, expect } from './pageTest';
|
||||
|
||||
test('toMatchText-based assertions should have matcher result', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
|
||||
{
|
||||
const e = await expect(locator).toHaveText(/Text2/, { timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'Text content',
|
||||
expected: /Text2/,
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toHaveText(expected)`),
|
||||
name: 'toHaveText',
|
||||
pass: false,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).toHaveText(expected)
|
||||
|
||||
Locator: locator('#node')
|
||||
Expected pattern: /Text2/
|
||||
Received string: \"Text content\"
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(locator).not.toHaveText(/Text/, { timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'Text content',
|
||||
expected: /Text/,
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toHaveText(expected)`),
|
||||
name: 'toHaveText',
|
||||
pass: true,
|
||||
});
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).not.toHaveText(expected)
|
||||
|
||||
Locator: locator('#node')
|
||||
Expected pattern: not /Text/
|
||||
Received string: \"Text content\"
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
test('toBeTruthy-based assertions should have matcher result', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#node2')).toBeVisible({ timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'hidden',
|
||||
expected: 'visible',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeVisible()`),
|
||||
name: 'toBeVisible',
|
||||
pass: false,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).toBeVisible()
|
||||
|
||||
Locator: locator('#node2')
|
||||
Expected: visible
|
||||
Received: hidden
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#node')).not.toBeVisible({ timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'visible',
|
||||
expected: 'visible',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeVisible()`),
|
||||
name: 'toBeVisible',
|
||||
pass: true,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).not.toBeVisible()
|
||||
|
||||
Locator: locator('#node')
|
||||
Expected: not visible
|
||||
Received: visible
|
||||
Call log`);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
test('toEqual-based assertions should have matcher result', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#node2')).toHaveCount(1, { timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toHaveCount(expected)`),
|
||||
name: 'toHaveCount',
|
||||
pass: false,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).toHaveCount(expected)
|
||||
|
||||
Locator: locator('#node2')
|
||||
Expected: 1
|
||||
Received: 0
|
||||
Call log`);
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#node')).not.toHaveCount(1, { timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toHaveCount(expected)`),
|
||||
name: 'toHaveCount',
|
||||
pass: true,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).not.toHaveCount(expected)
|
||||
|
||||
Locator: locator('#node')
|
||||
Expected: not 1
|
||||
Received: 1
|
||||
Call log`);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
test('toBeChecked({ checked: false }) should have expected: false', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<input id=checked type=checkbox checked></input>
|
||||
<input id=unchecked type=checkbox></input>
|
||||
`);
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#unchecked')).toBeChecked({ timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'unchecked',
|
||||
expected: 'checked',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeChecked()`),
|
||||
name: 'toBeChecked',
|
||||
pass: false,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).toBeChecked()
|
||||
|
||||
Locator: locator('#unchecked')
|
||||
Expected: checked
|
||||
Received: unchecked
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#checked')).not.toBeChecked({ timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'checked',
|
||||
expected: 'checked',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeChecked()`),
|
||||
name: 'toBeChecked',
|
||||
pass: true,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).not.toBeChecked()
|
||||
|
||||
Locator: locator('#checked')
|
||||
Expected: not checked
|
||||
Received: checked
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#checked')).toBeChecked({ checked: false, timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'checked',
|
||||
expected: 'unchecked',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeChecked({ checked: false })`),
|
||||
name: 'toBeChecked',
|
||||
pass: false,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).toBeChecked({ checked: false })
|
||||
|
||||
Locator: locator('#checked')
|
||||
Expected: unchecked
|
||||
Received: checked
|
||||
Call log`);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
const e = await expect(page.locator('#unchecked')).not.toBeChecked({ checked: false, timeout: 1 }).catch(e => e);
|
||||
e.matcherResult.message = stripAnsi(e.matcherResult.message);
|
||||
expect.soft(e.matcherResult).toEqual({
|
||||
locator: expect.any(Object),
|
||||
actual: 'unchecked',
|
||||
expected: 'unchecked',
|
||||
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeChecked({ checked: false })`),
|
||||
name: 'toBeChecked',
|
||||
pass: true,
|
||||
});
|
||||
|
||||
expect.soft(stripAnsi(e.toString())).toContain(`Error: Timed out 1ms waiting for expect(locator).not.toBeChecked({ checked: false })
|
||||
|
||||
Locator: locator('#unchecked')
|
||||
Expected: not unchecked
|
||||
Received: unchecked
|
||||
Call log`);
|
||||
|
||||
}
|
||||
});
|
|
@ -20,25 +20,25 @@ import { test, expect } from './pageTest';
|
|||
test('should print timed out error message', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const error = await expect(page.locator('no-such-thing')).toHaveText('hey', { timeout: 1000 }).catch(e => e);
|
||||
expect(stripAnsi(error.message)).toContain('Timed out 1000ms waiting for expect(received).toHaveText(expected)');
|
||||
expect(stripAnsi(error.message)).toContain(`Timed out 1000ms waiting for expect(locator).toHaveText(expected)`);
|
||||
});
|
||||
|
||||
test('should print timed out error message when value does not match', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const error = await expect(page.locator('div')).toHaveText('hey', { timeout: 1000 }).catch(e => e);
|
||||
expect(stripAnsi(error.message)).toContain('Timed out 1000ms waiting for expect(received).toHaveText(expected)');
|
||||
expect(stripAnsi(error.message)).toContain(`Timed out 1000ms waiting for expect(locator).toHaveText(expected)`);
|
||||
});
|
||||
|
||||
test('should print timed out error message with impossible timeout', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const error = await expect(page.locator('no-such-thing')).toHaveText('hey', { timeout: 1 }).catch(e => e);
|
||||
expect(stripAnsi(error.message)).toContain('Timed out 1ms waiting for expect(received).toHaveText(expected)');
|
||||
expect(stripAnsi(error.message)).toContain(`Timed out 1ms waiting for expect(locator).toHaveText(expected)`);
|
||||
});
|
||||
|
||||
test('should print timed out error message when value does not match with impossible timeout', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const error = await expect(page.locator('div')).toHaveText('hey', { timeout: 1 }).catch(e => e);
|
||||
expect(stripAnsi(error.message)).toContain('Timed out 1ms waiting for expect(received).toHaveText(expected)');
|
||||
expect(stripAnsi(error.message)).toContain(`Timed out 1ms waiting for expect(locator).toHaveText(expected)`);
|
||||
});
|
||||
|
||||
test('should not print timed out error message when page closes', async ({ page }) => {
|
||||
|
|
|
@ -24,10 +24,10 @@ it('should have a nice preview', async ({ page, server }) => {
|
|||
const check = page.locator('#check');
|
||||
const text = await inner.evaluateHandle(e => e.firstChild);
|
||||
await page.evaluate(() => 1); // Give them a chance to calculate the preview.
|
||||
expect(String(outer)).toBe('Locator@#outer');
|
||||
expect(String(inner)).toBe('Locator@#outer >> #inner');
|
||||
expect(String(text)).toBe('JSHandle@#text=Text,↵more text');
|
||||
expect(String(check)).toBe('Locator@#check');
|
||||
expect.soft(String(outer)).toBe(`locator('#outer')`);
|
||||
expect.soft(String(inner)).toBe(`locator('#outer').locator('#inner')`);
|
||||
expect.soft(String(text)).toBe(`JSHandle@#text=Text,↵more text`);
|
||||
expect.soft(String(check)).toBe(`locator('#check')`);
|
||||
});
|
||||
|
||||
it('getAttribute should work', async ({ page, server }) => {
|
||||
|
|
|
@ -47,11 +47,9 @@ test('should configure message', async ({ runInlineTest }) => {
|
|||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain([
|
||||
` Error: x-foo must be visible`,
|
||||
``,
|
||||
` Call log:`,
|
||||
].join('\n'));
|
||||
expect(result.output).toContain('Error: x-foo must be visible');
|
||||
expect(result.output).toContain(`Timed out 1ms waiting for expect(locator).toBeVisible()`);
|
||||
expect(result.output).toContain('Call log:');
|
||||
});
|
||||
|
||||
test('should prefer local message', async ({ runInlineTest }) => {
|
||||
|
@ -66,11 +64,10 @@ test('should prefer local message', async ({ runInlineTest }) => {
|
|||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain([
|
||||
` Error: overridden`,
|
||||
``,
|
||||
` Call log:`,
|
||||
].join('\n'));
|
||||
|
||||
expect(result.output).toContain('Error: overridden');
|
||||
expect(result.output).toContain(`Timed out 1ms waiting for expect(locator).toBeVisible()`);
|
||||
expect(result.output).toContain('Call log:');
|
||||
});
|
||||
|
||||
test('should configure soft', async ({ runInlineTest }) => {
|
||||
|
|
|
@ -81,8 +81,8 @@ test('should include custom error message', async ({ runInlineTest }) => {
|
|||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain([
|
||||
` Error: one plus one is two!`,
|
||||
``,
|
||||
` Error: one plus one is two!\n`,
|
||||
` expect(received).toEqual(expected) // deep equality\n`,
|
||||
` Expected: 3`,
|
||||
` Received: 2`,
|
||||
].join('\n'));
|
||||
|
@ -99,11 +99,10 @@ test('should include custom error message with web-first assertions', async ({ r
|
|||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain([
|
||||
` Error: x-foo must be visible`,
|
||||
``,
|
||||
` Call log:`,
|
||||
].join('\n'));
|
||||
|
||||
expect(result.output).toContain('Error: x-foo must be visible');
|
||||
expect(result.output).toContain(`Timed out 1ms waiting for expect(locator).toBeVisible()`);
|
||||
expect(result.output).toContain('Call log:');
|
||||
});
|
||||
|
||||
test('should work with generic matchers', async ({ runTSC }) => {
|
||||
|
@ -627,7 +626,7 @@ test('should print pending operations for toHaveText', async ({ runInlineTest })
|
|||
expect(result.exitCode).toBe(1);
|
||||
const output = result.output;
|
||||
expect(output).toContain('Pending operations:');
|
||||
expect(output).toContain('expect(received).toHaveText(expected)');
|
||||
expect(output).toContain(`expect(locator).toHaveText(expected)`);
|
||||
expect(output).toContain('Expected string: "Text"');
|
||||
expect(output).toContain('Received string: ""');
|
||||
expect(output).toContain('waiting for locator(\'no-such-thing\')');
|
||||
|
@ -677,7 +676,7 @@ test('should not print timed out error message when test times out', async ({ ru
|
|||
const output = result.output;
|
||||
expect(output).toContain('Test timeout of 3000ms exceeded');
|
||||
expect(output).not.toContain('Timed out 5000ms waiting for expect');
|
||||
expect(output).toContain('Error: expect(received).toHaveText(expected)');
|
||||
expect(output).toContain(`Error: expect(locator).toHaveText(expected)`);
|
||||
});
|
||||
|
||||
test('should not leak long expect message strings', async ({ runInlineTest }) => {
|
||||
|
|
|
@ -600,7 +600,7 @@ test('should run CT on changed deps', async ({ runWatchTest, writeFiles }) => {
|
|||
|
||||
await testProcess.waitForOutput(`src${path.sep}button.spec.tsx:4:11 › pass`);
|
||||
expect(testProcess.output).not.toContain(`src${path.sep}link.spec.tsx`);
|
||||
await testProcess.waitForOutput('Error: Timed out 1000ms waiting for expect(received).toHaveText(expected)');
|
||||
await testProcess.waitForOutput(`Error: Timed out 1000ms waiting for expect(locator).toHaveText(expected)`);
|
||||
await testProcess.waitForOutput('Waiting for file changes.');
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче