npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ex-pw

v0.0.3

Published

Extended Playwright expect matchers with auto-waiting and improved developer experience

Readme

ex-pw

Extended Playwright expect matchers with auto-waiting and improved developer experience.

npm version

Installation

npm install -D ex-pw

Setup

Extend in Playwright Config

Update your playwright.config.ts:

import { expect } from "@playwright/test";
import exPw from "ex-pw";

// Extend expect globally
expect.extend(exPw);

Locator Matchers

toBeClickable

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 button

toBeCheckable

Asserts 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();

toBeRequired

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();

toBeInvalid

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();

toBeValid

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();

toHaveCountGreaterThan

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);

toHaveCountGreaterThanOrEqual

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);

toHaveCountLessThan

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);

toHaveCountLessThanOrEqual

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);

toHaveWidth

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);

toHaveHeight

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);

toHaveSize

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);

toHaveLoadedImage

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();

Page Matchers

toHaveCookie

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" });

toHaveLocalStorage

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" },
});

toHaveSessionStorage

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 }]),
});

toHaveClipboardText

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/);

toHaveRequest

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 });

toHaveConsoleMessage

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/,
});

toHavePageError

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 expected

toHaveNoSoftErrors

For 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:/ });

General Matchers

toBeSorted

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 });

API Matchers

toMatchJSON

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"),
});

toMatchSchema

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);

toHaveStatus

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 2xx

toHaveHeader

Checks 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/ });

toRespondWithin

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

Asymmetric matchers can be used both in expect().toEqual() assertions and as standalone matchers.

String Matchers

toStartWith

Asserts that a string starts with the expected prefix.

expect("JohnDoe").toStartWith("John");
expect(data).toEqual({ name: expect.toStartWith("John") });

toEndWith

Asserts that a string ends with the expected suffix.

expect("[email protected]").toEndWith("@example.com");
expect(data).toEqual({ email: expect.toEndWith("@example.com") });

toBeUpperCase

Asserts that a string is entirely uppercase.

expect("HELLO").toBeUpperCase();
expect(data).toEqual({ code: expect.toBeUpperCase() });

toBeLowerCase

Asserts that a string is entirely lowercase.

expect("hello").toBeLowerCase();
expect(data).toEqual({ slug: expect.toBeLowerCase() });

Case Format Matchers

toBePascalCase

Asserts that a string is in PascalCase format (e.g., MyClassName).

expect("MyClassName").toBePascalCase();
expect(data).toEqual({ className: expect.toBePascalCase() });

toBeCamelCase

Asserts that a string is in camelCase format (e.g., myMethodName).

expect("myMethodName").toBeCamelCase();
expect(data).toEqual({ methodName: expect.toBeCamelCase() });

toBeKebabCase

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() });

toBeSnakeCase

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() });

Validation Matchers

toBeEmail

Asserts that a string is a valid email address.

expect("[email protected]").toBeEmail();
expect(data).toEqual({ email: expect.toBeEmail() });

toBeURL

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" });

toBeUUID

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");

toBeJSON

Asserts that a string is valid JSON.

expect('{"key": "value"}').toBeJSON();

Date Matchers

toBeISODate

Asserts that a string is a valid ISO 8601 date.

expect("2024-01-15T10:30:00.000Z").toBeISODate();

toBeDateString

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");

Number Matchers

toBeWithinRange

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) });

License

MIT