Beginning of toHaveURL with predicate

This commit is contained in:
Adam Gastineau 2025-01-16 12:24:14 -08:00
parent 84bbc5fd35
commit afd19da215
4 changed files with 109 additions and 20 deletions

View file

@ -0,0 +1,50 @@
/**
* 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 { ExpectMatcherState } from '../../types/test';
import { matcherHint } from './matcherHint';
import { colors } from 'playwright-core/lib/utilsBundle';
import type { Locator } from 'playwright-core';
import { EXPECTED_COLOR } from '../common/expectBundle';
export function toMatchExpectedVerification(
state: ExpectMatcherState,
matcherName: string,
receiver: Locator | undefined,
expression: string | Locator | undefined,
expected: string | RegExp | Function,
supportsPredicate: boolean = false
): void {
const matcherOptions = {
isNot: state.isNot,
promise: state.promise,
};
if (
!(typeof expected === 'string') &&
!(expected && 'test' in expected && typeof expected.test === 'function') &&
!(supportsPredicate && typeof expected === 'function')
) {
// Same format as jest's matcherErrorMessage
const message = supportsPredicate ? 'string, regular expression, or predicate' : 'string or regular expression';
throw new Error([
matcherHint(state, receiver, matcherName, expression, expected, matcherOptions),
`${colors.bold('Matcher error')}: ${EXPECTED_COLOR('expected',)} value must be a ${message}`,
state.utils.printWithType('Expected', expected, state.utils.printExpected)
].join('\n\n'));
}
}

View file

@ -47,7 +47,8 @@ import {
toHaveURL,
toHaveValue,
toHaveValues,
toPass
toPass,
toHaveURL2
} from './matchers';
import { toMatchSnapshot, toHaveScreenshot, toHaveScreenshotStepTitle } from './toMatchSnapshot';
import type { Expect, ExpectMatcherState } from '../../types/test';
@ -235,6 +236,7 @@ const customAsyncMatchers = {
toHaveText,
toHaveTitle,
toHaveURL,
toHaveURL2,
toHaveValue,
toHaveValues,
toHaveScreenshot,

View file

@ -26,6 +26,8 @@ import { currentTestInfo } from '../common/globals';
import { TestInfoImpl } from '../worker/testInfo';
import type { ExpectMatcherState } from '../../types/test';
import { takeFirst } from '../common/config';
import { matcherHint } from './matcherHint';
import { toMatchExpectedVerification } from './error';
export interface LocatorEx extends Locator {
_expect(expression: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }>;
@ -386,6 +388,54 @@ export function toHaveTitle(
}, expected, options);
}
export async function toHaveURL2(
this: ExpectMatcherState,
page: Page,
expected: string | RegExp | ((url: URL) => boolean),
options?: { ignoreCase?: boolean; timeout?: number },
) {
const matcherName = 'toHaveURL';
const expression = 'page';
toMatchExpectedVerification(
this,
matcherName,
undefined,
expression,
expected,
true,
);
const timeout = options?.timeout ?? this.timeout;
let urlMatched = false;
try {
await page.mainFrame().waitForURL(expected, { timeout });
urlMatched = true;
} catch (e) {
urlMatched = false;
}
if (urlMatched !== this.isNot)
return { pass: urlMatched, message: () => '' };
const matcherOptions = {
isNot: this.isNot,
promise: this.promise,
};
return {
pass: urlMatched,
message: () =>
matcherHint(
this,
undefined,
matcherName,
expression,
typeof expected === 'function' ? 'predicate' : expected,
matcherOptions,
timeout,
),
};
}
export function toHaveURL(
this: ExpectMatcherState,
page: Page,

View file

@ -20,12 +20,11 @@ import {
printReceivedStringContainExpectedResult,
printReceivedStringContainExpectedSubstring
} from './expect';
import { EXPECTED_COLOR } from '../common/expectBundle';
import type { ExpectMatcherState } from '../../types/test';
import { kNoElementsFoundError, matcherHint } from './matcherHint';
import type { MatcherResult } from './matcherHint';
import type { Locator } from 'playwright-core';
import { colors } from 'playwright-core/lib/utilsBundle';
import { toMatchExpectedVerification } from './error';
export async function toMatchText(
this: ExpectMatcherState,
@ -37,23 +36,7 @@ export async function toMatchText(
options: { timeout?: number, matchSubstring?: boolean } = {},
): Promise<MatcherResult<string | RegExp, string>> {
expectTypes(receiver, [receiverType], matcherName);
const matcherOptions = {
isNot: this.isNot,
promise: this.promise,
};
if (
!(typeof expected === 'string') &&
!(expected && typeof expected.test === 'function')
) {
// Same format as jest's matcherErrorMessage
throw new Error([
matcherHint(this, receiver, matcherName, receiver, expected, matcherOptions),
`${colors.bold('Matcher error')}: ${EXPECTED_COLOR('expected',)} value must be a string or regular expression`,
this.utils.printWithType('Expected', expected, this.utils.printExpected)
].join('\n\n'));
}
toMatchExpectedVerification(this, matcherName, receiver, receiver, expected);
const timeout = options.timeout ?? this.timeout;
@ -67,6 +50,10 @@ export async function toMatchText(
};
}
const matcherOptions = {
isNot: this.isNot,
promise: this.promise,
};
const stringSubstring = options.matchSubstring ? 'substring' : 'string';
const receivedString = received || '';
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);