Extended Playwright expect matchers with auto-waiting and improved developer experience.
npm install -D ex-pwUpdate your playwright.config.ts:
import { expect } from "@playwright/test";
import exPw from "ex-pw";
// Extend expect globally
expect.extend(exPw);Asserts that an element is in a clickable state (visible, enabled, and not obscured).
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
await expect(page.getByRole("button")).toBeClickable();
await expect(page.getByRole("button")).toBeClickable({ timeout: 5000 });
await expect(page.getByRole("button")).not.toBeClickable(); // disabled buttonAsserts that a checkbox or radio is in a checkable state.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
await expect(page.getByRole("checkbox")).toBeCheckable();
await expect(page.getByRole("radio")).toBeCheckable();Asserts that a form element is required.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.getByLabel("Email")).toBeRequired();
await expect(page.getByLabel("Optional")).not.toBeRequired();Asserts that a form element is in an invalid validation state.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.getByLabel("Email")).toBeInvalid();Asserts that a form element is in a valid validation state.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.getByLabel("Email")).toBeValid();Asserts that a locator matches more than the specified number of elements.
| Option | Type | Description |
|---|---|---|
count |
number |
The count threshold (required) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator(".item")).toHaveCountGreaterThan(3);Asserts that a locator matches at least the specified number of elements.
| Option | Type | Description |
|---|---|---|
count |
number |
The count threshold (required) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator(".item")).toHaveCountGreaterThanOrEqual(5);Asserts that a locator matches fewer than the specified number of elements.
| Option | Type | Description |
|---|---|---|
count |
number |
The count threshold (required) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator(".item")).toHaveCountLessThan(10);Asserts that a locator matches at most the specified number of elements.
| Option | Type | Description |
|---|---|---|
count |
number |
The count threshold (required) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator(".item")).toHaveCountLessThanOrEqual(5);Asserts that an element has the expected width.
| Option | Type | Description |
|---|---|---|
expected |
number |
Expected width in pixels |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator("#box")).toHaveWidth(100);Asserts that an element has the expected height.
| Option | Type | Description |
|---|---|---|
expected |
number |
Expected height in pixels |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator("#box")).toHaveHeight(50);Asserts that an element has the expected width and height.
| Option | Type | Description |
|---|---|---|
width |
number |
Expected width in pixels |
height |
number |
Expected height in pixels |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator("#box")).toHaveSize(100, 50);Asserts that an <img> element has successfully loaded its image.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page.locator("img#logo")).toHaveLoadedImage();
await expect(page.locator("img#broken")).not.toHaveLoadedImage();Asserts that a cookie exists with optional value/domain matching.
| Option | Type | Description |
|---|---|---|
name |
string |
Cookie name (required) |
value |
string | RegExp |
Expected cookie value |
domain |
string |
Expected cookie domain |
timeout |
number |
Maximum time to wait in ms |
await expect(page).toHaveCookie("session");
await expect(page).toHaveCookie("session", { value: "abc123" });
await expect(page).toHaveCookie("session", { value: /^abc/ });
await expect(page).toHaveCookie("auth", { domain: "example.com" });Asserts that localStorage contains a key with optional value matching.
| Option | Type | Description |
|---|---|---|
key |
string |
Storage key (required) |
value |
any |
Expected value (parsed as JSON) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page).toHaveLocalStorage("authToken");
await expect(page).toHaveLocalStorage("settings", {
value: { theme: "dark" },
});Asserts that sessionStorage contains a key with optional value matching.
| Option | Type | Description |
|---|---|---|
key |
string |
Storage key (required) |
value |
any |
Expected value (parsed as JSON) |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
await expect(page).toHaveSessionStorage("tempData");
await expect(page).toHaveSessionStorage("cart", {
value: expect.arrayContaining([{ id: 1 }]),
});Asserts that the clipboard contains the expected text.
| Option | Type | Description |
|---|---|---|
expected |
string | RegExp |
Expected clipboard text |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
Note: Requires
permissions: ["clipboard-read"]in your Playwright config.
// playwright.config.ts
export default defineConfig({
use: {
permissions: ["clipboard-read"],
},
});
// In tests
await expect(page).toHaveClipboardText("copied text");
await expect(page).toHaveClipboardText(/pattern/);Asserts that the page has made a network request matching the criteria.
| Option | Type | Description |
|---|---|---|
url |
string | RegExp |
URL pattern to match |
method |
string |
HTTP method to match |
status |
number |
Expected response status |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
Limitations: Only returns up to 100 most recent requests. Requests may be garbage collected if not accessed promptly.
await expect(page).toHaveRequest({ url: /api\/users/ });
await expect(page).toHaveRequest({ method: "POST", url: /api\/login/ });
await expect(page).toHaveRequest({ url: "example.com/api", status: 200 });Asserts that the page has a console message matching the criteria.
| Option | Type | Description |
|---|---|---|
type |
string |
Console message type |
text |
string | RegExp |
Message text pattern |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
Limitations: Only returns up to 200 most recent console messages.
await expect(page).toHaveConsoleMessage({ text: "Hello" });
await expect(page).toHaveConsoleMessage({ type: "error" });
await expect(page).toHaveConsoleMessage({
type: "warning",
text: /deprecated/,
});Asserts that the page has encountered a JavaScript error.
| Option | Type | Description |
|---|---|---|
message |
string | RegExp |
Error message pattern |
name |
string |
Error name to match |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
Limitations: Only returns up to 200 most recent errors. Only captures uncaught exceptions.
await expect(page).toHavePageError();
await expect(page).toHavePageError({ message: "undefined is not a function" });
await expect(page).toHavePageError({ message: /TypeError/ });
await expect(page).not.toHavePageError(); // No errors expectedFor use with soft assertions - checks for test errors.
| Option | Type | Description |
|---|---|---|
ignore |
RegExp |
Pattern for errors to ignore |
await expect.soft(page.getByTestId("status")).toHaveText("Success");
await expect.soft(page.getByTestId("eta")).toHaveText("1 day");
expect(test).toHaveNoSoftErrors();
// Ignore specific errors
expect(test).toHaveNoSoftErrors({ ignore: /Warning:/ });Asserts that an array or locator elements are sorted.
| Option | Type | Description |
|---|---|---|
descending |
boolean |
Sort in descending order |
key |
string | Function |
Extract value from objects |
compareAsNumbers |
boolean |
Parse text as numbers for comparison |
useTextContent |
boolean |
Use allTextContents() instead |
timeout |
number |
Maximum time to wait in ms |
intervals |
number[] |
Retry intervals in ms |
// With arrays
await expect([1, 2, 3]).toBeSorted();
await expect(["c", "b", "a"]).toBeSorted({ descending: true });
await expect([{ val: 1 }, { val: 2 }]).toBeSorted({ key: "val" });
// With Locators - extracts text from elements
await expect(page.locator(".item")).toBeSorted();
await expect(page.locator(".price")).toBeSorted({ compareAsNumbers: true });
await expect(page.locator(".name")).toBeSorted({ descending: true });Asserts that the response body matches expected JSON. Supports asymmetric matchers for flexible field validation.
| Option | Type | Description |
|---|---|---|
expected |
any |
Expected JSON (required) |
const response = await request.get("/api/user");
// Exact match
await expect(response).toMatchJSON({ id: 1, name: "John" });
// Partial match with Playwright's built-in matchers
await expect(response).toMatchJSON(expect.objectContaining({ id: 1 }));
// With ex-pw asymmetric matchers
await expect(response).toMatchJSON({
id: expect.toBeUUID(),
email: expect.toBeEmail(),
createdAt: expect.toBeISODate(),
name: expect.toStartWith("John"),
});Validates response body against a Zod schema.
| Option | Type | Description |
|---|---|---|
schema |
ZodSchema |
Zod schema (required) |
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
const response = await request.get("/api/user");
await expect(response).toMatchSchema(UserSchema);Checks HTTP status code or range.
| Option | Type | Description |
|---|---|---|
expected |
number | { min, max } |
Status code or range (required) |
await expect(response).toHaveStatus(200);
await expect(response).toHaveStatus({ min: 200, max: 299 }); // Any 2xxChecks for HTTP header existence and optional value.
| Option | Type | Description |
|---|---|---|
name |
string |
Header name (required) |
value |
string | RegExp |
Expected header value |
await expect(response).toHaveHeader("content-type");
await expect(response).toHaveHeader("content-type", {
value: "application/json",
});
await expect(response).toHaveHeader("content-type", { value: /json/ });Asserts that a request completes within the specified time.
| Option | Type | Description |
|---|---|---|
timeout |
number |
Max response time in ms |
// Don't await the request - pass the promise directly
const request = page.request.get("/api/fast");
await expect(request).toRespondWithin(1000);Asymmetric matchers can be used both in expect().toEqual() assertions and as
standalone matchers.
Asserts that a string starts with the expected prefix.
expect("JohnDoe").toStartWith("John");
expect(data).toEqual({ name: expect.toStartWith("John") });Asserts that a string ends with the expected suffix.
expect("john@example.com").toEndWith("@example.com");
expect(data).toEqual({ email: expect.toEndWith("@example.com") });Asserts that a string is entirely uppercase.
expect("HELLO").toBeUpperCase();
expect(data).toEqual({ code: expect.toBeUpperCase() });Asserts that a string is entirely lowercase.
expect("hello").toBeLowerCase();
expect(data).toEqual({ slug: expect.toBeLowerCase() });Asserts that a string is in PascalCase format (e.g., MyClassName).
expect("MyClassName").toBePascalCase();
expect(data).toEqual({ className: expect.toBePascalCase() });Asserts that a string is in camelCase format (e.g., myMethodName).
expect("myMethodName").toBeCamelCase();
expect(data).toEqual({ methodName: expect.toBeCamelCase() });Asserts that a string is in kebab-case format (e.g., my-css-class).
expect("my-css-class").toBeKebabCase();
expect(data).toEqual({ cssClass: expect.toBeKebabCase() });Asserts that a string is in snake_case format (e.g., my_variable_name).
expect("my_variable_name").toBeSnakeCase();
expect(data).toEqual({ envVar: expect.toBeSnakeCase() });Asserts that a string is a valid email address.
expect("user@example.com").toBeEmail();
expect(data).toEqual({ email: expect.toBeEmail() });Asserts that a string is a valid URL.
| Option | Type | Description |
|---|---|---|
protocol |
string |
Required protocol (e.g., https) |
expect("https://example.com").toBeURL();
expect("https://example.com").toBeURL({ protocol: "https" });Asserts that a string is a valid UUID.
| Option | Type | Description |
|---|---|---|
version |
string |
UUID version (e.g., "v4") |
expect("550e8400-e29b-41d4-a716-446655440000").toBeUUID();
expect(id).toBeUUID("v4");Asserts that a string is valid JSON.
expect('{"key": "value"}').toBeJSON();Asserts that a string is a valid ISO 8601 date.
expect("2024-01-15T10:30:00.000Z").toBeISODate();Asserts that a string matches a custom date format.
| Option | Type | Description |
|---|---|---|
format |
string |
Date format pattern (required) |
expect("2024-01-15").toBeDateString("YYYY-MM-DD");
expect("01/15/2024").toBeDateString("MM/DD/YYYY");Asserts that a number is within the specified range (inclusive).
| Option | Type | Description |
|---|---|---|
min |
number |
Minimum value (required) |
max |
number |
Maximum value (required) |
expect(5).toBeWithinRange(1, 10);
expect(response).toEqual({ count: expect.toBeWithinRange(0, 100) });MIT