Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,6 @@ export class InjectedScript {
} else if (expression === 'to.have.title') {
received = this.document.title;
} else if (expression === 'to.have.url') {
// Note: this is used by all language ports except for javascript.
received = this.document.location.href;
} else if (expression === 'to.have.value') {
element = this.retarget(element, 'follow-label')!;
Expand Down
16 changes: 13 additions & 3 deletions packages/playwright/src/matchers/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* limitations under the License.
*/

import { isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils';
import { constructURLBasedOnBaseURL, isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils';
import { colors } from 'playwright-core/lib/utils';

import { callLogText, expectTypes } from '../util';
import { toBeTruthy } from './toBeTruthy';
import { toEqual } from './toEqual';
import { toHaveURL as toHaveURLExternal } from './toHaveURL';
import { toHaveURLWithPredicate } from './toHaveURL';
import { toMatchText } from './toMatchText';
import { takeFirst } from '../common/config';
import { currentTestInfo } from '../common/globals';
Expand Down Expand Up @@ -391,7 +391,17 @@ export function toHaveURL(
expected: string | RegExp | ((url: URL) => boolean),
options?: { ignoreCase?: boolean; timeout?: number },
) {
return toHaveURLExternal.call(this, page, expected, options);
// Ports don't support predicates. Keep separate server and client codepaths
if (typeof expected === 'function')
return toHaveURLWithPredicate.call(this, page, expected, options);

const baseURL = (page.context() as any)._options.baseURL;
expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected;
const locator = page.locator(':root') as LocatorEx;
return toMatchText.call(this, 'toHaveURL', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
return await locator._expect('to.have.url', { expectedText, isNot, timeout });
}, expected, options);
}

export async function toBeOK(
Expand Down
36 changes: 11 additions & 25 deletions packages/playwright/src/matchers/toHaveURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
* limitations under the License.
*/

import { constructURLBasedOnBaseURL, urlMatches } from 'playwright-core/lib/utils';
import { urlMatches } from 'playwright-core/lib/utils';
import { colors } from 'playwright-core/lib/utils';

import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring } from './expect';
import { printReceivedStringContainExpectedResult } from './expect';
import { matcherHint } from './matcherHint';
import { EXPECTED_COLOR, printReceived } from '../common/expectBundle';

import type { MatcherResult } from './matcherHint';
import type { ExpectMatcherState } from '../../types/test';
import type { Page } from 'playwright-core';

export async function toHaveURL(
export async function toHaveURLWithPredicate(
this: ExpectMatcherState,
page: Page,
expected: string | RegExp | ((url: URL) => boolean),
expected: (url: URL) => boolean,
options?: { ignoreCase?: boolean; timeout?: number },
): Promise<MatcherResult<string | RegExp, string>> {
const matcherName = 'toHaveURL';
Expand All @@ -38,11 +38,7 @@ export async function toHaveURL(
promise: this.promise,
};

if (
!(typeof expected === 'string') &&
!(expected && 'test' in expected && typeof expected.test === 'function') &&
!(typeof expected === 'function')
) {
if (typeof expected !== 'function') {
throw new Error(
[
// Always display `expected` in expectation place
Expand All @@ -68,9 +64,7 @@ export async function toHaveURL(
urlMatches(
baseURL?.toLocaleLowerCase(),
lastCheckedURLString.toLocaleLowerCase(),
typeof expected === 'string'
? expected.toLocaleLowerCase()
: expected,
expected,
)
);
}
Expand Down Expand Up @@ -98,9 +92,7 @@ export async function toHaveURL(
this,
matcherName,
expression,
typeof expected === 'string'
? constructURLBasedOnBaseURL(baseURL, expected)
: expected,
expected,
lastCheckedURLString,
this.isNot,
true,
Expand All @@ -115,7 +107,7 @@ function toHaveURLMessage(
state: ExpectMatcherState,
matcherName: string,
expression: string,
expected: string | RegExp | Function,
expected: Function,
received: string | undefined,
pass: boolean,
didTimeout: boolean,
Expand All @@ -136,15 +128,9 @@ function toHaveURLMessage(
printedReceived = `Received string: ${printReceived(receivedString)}`;
} else {
if (pass) {
if (typeof expected === 'string') {
printedExpected = `Expected string: not ${state.utils.printExpected(expected)}`;
const formattedReceived = printReceivedStringContainExpectedSubstring(receivedString, receivedString.indexOf(expected), expected.length);
printedReceived = `Received string: ${formattedReceived}`;
} else {
printedExpected = `Expected pattern: not ${state.utils.printExpected(expected)}`;
const formattedReceived = printReceivedStringContainExpectedResult(receivedString, typeof expected.exec === 'function' ? expected.exec(receivedString) : null);
printedReceived = `Received string: ${formattedReceived}`;
}
printedExpected = `Expected pattern: not ${state.utils.printExpected(expected)}`;
const formattedReceived = printReceivedStringContainExpectedResult(receivedString, null);
printedReceived = `Received string: ${formattedReceived}`;
} else {
const labelExpected = `Expected ${typeof expected === 'string' ? 'string' : 'pattern'}`;
printedDiff = state.utils.printDiffOrStringify(expected, receivedString, labelExpected, 'Received string', false);
Expand Down
4 changes: 2 additions & 2 deletions tests/page/expect-misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,15 @@ test.describe('toHaveURL', () => {
test('fail string', async ({ page }) => {
await page.goto('data:text/html,<div>A</div>');
const error = await expect(page).toHaveURL('wrong', { timeout: 1000 }).catch(e => e);
expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)');
expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(locator).toHaveURL(expected)');
expect(stripVTControlCharacters(error.message)).toContain('Expected string: "wrong"\nReceived string: "data:text/html,<div>A</div>"');
});

test('fail with invalid argument', async ({ page }) => {
await page.goto('data:text/html,<div>A</div>');
// @ts-expect-error
const error = await expect(page).toHaveURL({}).catch(e => e);
expect(stripVTControlCharacters(error.message)).toContain('expect(page).toHaveURL(expected)\n\n\n\nMatcher error: expected value must be a string, regular expression, or predicate');
expect(stripVTControlCharacters(error.message)).toContain(`expect(locator(':root')).toHaveURL([object Object])`);
expect(stripVTControlCharacters(error.message)).toContain('Expected has type: object\nExpected has value: {}');
});

Expand Down
4 changes: 2 additions & 2 deletions tests/playwright-test/expect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ test('should respect expect.timeout', async ({ runInlineTest }) => {
test('timeout', async ({ page }) => {
await page.goto('data:text/html,<div>A</div>');
const error = await expect(page).toHaveURL('data:text/html,<div>B</div>').catch(e => e);
expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)');
expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(locator).toHaveURL(expected)');
expect(error.message).toContain('data:text/html,<div>');
});
`,
Expand All @@ -566,7 +566,7 @@ test('should support toHaveURL predicate', async ({ runInlineTest }) => {

test('predicate', async ({ page }) => {
await page.goto('data:text/html,<div>A</div>');
const error = await expect(page).toHaveURL('data:text/html,<div>B</div>').catch(e => e);
const error = await expect(page).toHaveURL(url => url === 'data:text/html,<div>B</div>').catch(e => e);
expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)');
expect(error.message).toContain('data:text/html,<div>');
});
Expand Down
Loading