How can I use playwright expect
to check for one of two exact matches?
Here's my function.
export const assertThirdPartyInternetPath = async (
page: Page,
path: string,
) => {
expect(page.url()).toBe(path);
};
I am using it to test links to wikipedia pages.
await this.assertThirdPartyInternetPath('https://en.wikipedia.org/wiki/Larry_Sanger'
However, some sites like Wikipedia will redirect mobile devices (including playwright devices) to the m
subdomain.
So I want to assert that the user is at either https://en.wikipedia.org/wiki/Larry_Sanger
or https://en.m.wikipedia.org/wiki/Larry_Sanger
. How can I do that?
Note that I want to do an exact match; I know I can use expect(string.toContain(myPattern)
but I have various things to match and I want to do exact matches.
Reversing the comparison as suggested in Jest matcher to match any one of three values is possible, but makes the assertion message and overall flow a bit awkward.
expect([
"https://en.wikipedia.org/wiki/Larry_Sanger",
"https://en.m.wikipedia.org/wiki/Larry_Sanger",
]).toContain(page.url());
I'd prefer to use regex because it keeps the assertion in the normal direction:
expect(page.url())
.toMatch(/^https:\/\/en\.(?:m\.)?wikipedia\.org\/wiki\/Larry_Sanger$/);
The problems are readability and remembering to escape regex characters and add anchors. You could use a normal string and escape it with a bit of extra utility code.
Another option is to write a custom matcher:
import {expect, test} from "@playwright/test"; // ^1.39.0
expect.extend({
async toBeAnyOf(received, ...possibilities) {
if (possibilities.includes(received)) {
return {
message: () => "passed",
pass: true,
};
}
return {
message: () =>
`failed: '${received}' was not any of ${possibilities}`,
pass: false,
};
},
});
test("is on the normal site", async ({page}) => {
await page.goto("https://en.wikipedia.org/wiki/Larry_Sanger");
expect(page.url()).toBeAnyOf(
"https://en.wikipedia.org/wiki/Larry_Sanger",
"https://en.m.wikipedia.org/wiki/Larry_Sanger",
);
});
test("is on the mobile site", async ({page}) => {
await page.goto("https://en.m.wikipedia.org/wiki/Larry_Sanger");
expect(page.url()).toBeAnyOf(
"https://en.wikipedia.org/wiki/Larry_Sanger",
"https://en.m.wikipedia.org/wiki/Larry_Sanger",
);
});
If you need auto-waiting, the regex will work in toHaveURL
:
await expect(page).toHaveURL(
/^https:\/\/en\.(?:m\.)?wikipedia\.org\/wiki\/Larry_Sanger$/
);
Promise.race()
is also possible. This avoids the regex and is pretty clear to read, but with the downside of being a bit verbose:
import {expect, test} from "@playwright/test";
test("navigate to one of two URLs", async ({page}) => {
const url = "https://en.wikipedia.org/wiki/Larry_Sanger";
const mobile = "https://en.m.wikipedia.org/wiki/Larry_Sanger";
await page.goto(Math.random() >= 0.5 ? url : mobile);
await Promise.race([
expect(page).toHaveURL(url),
expect(page).toHaveURL(mobile),
]);
});
You can shorten this with a macro for Promise.race()
if you're using it often:
const either = (...a) => Promise.race([...a]);
// ...
await either(
expect(page).toHaveURL(url),
expect(page).toHaveURL(mobile)
);
Note that Promise.race()
doesn't have a timeout, so if none of the options ever match, you'd have to rely on the test case timing out. This might not show as clear a failure message as it could.