chore: populate matcherResult in web assertions (#27133)

Ref https://github.com/microsoft/playwright/issues/26929
This commit is contained in:
Pavel Feldman 2023-09-16 14:24:10 -07:00 коммит произвёл GitHub
Родитель 3bcf9687e6
Коммит 9fe037fb63
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 357 добавлений и 88 удалений

Просмотреть файл

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