@cerios/playwright-expectly-fuzzy
v0.1.1
Published
Fuzzy string matching matchers for Playwright using fuzzball. Ideal for validating AI-generated text where wording may vary.
Readme
🎭 Playwright Expectly Fuzzy | By Cerios
Fuzzy string matching matchers for Playwright. Validates AI-generated text, chatbot replies, and dynamic content where exact wording may vary — using fuzzball's token_sort_ratio algorithm.
Part of the playwright-expectly ecosystem.
Features
- 🤖 AI-Ready — Tolerates minor wording variations, typos, and rephrasing
- 🔀 Word-order-insensitive —
"hello world"and"world hello"both score 100 - 🎯 Configurable threshold — 0–100 similarity score, default 80
- 🎨 Locator support — Works directly on Playwright
Locatorobjects with automatic polling - 💪 Type-Safe — Full TypeScript support with Playwright
expecttype augmentation - ⚡ Lightweight — Only Playwright and
fuzzballrequired
Installation
npm install @cerios/playwright-expectly-fuzzy --save-devPeer dependency: requires
@playwright/testto be installed.
Quick Start
Option 1: expectlyFuzzy (standalone)
Use expectlyFuzzy directly without modifying Playwright's expect:
import { expectlyFuzzy } from "@cerios/playwright-expectly-fuzzy";
// Passes — minor typo tolerated (default threshold 80)
expectlyFuzzy("Hello Wrold").toMatchFuzzy("Hello World");
// Word-order-insensitive — scores 100
expectlyFuzzy("world hello").toMatchFuzzy("hello world");
// Custom threshold for more lenient matching
expectlyFuzzy("The cat sat on the mat").toMatchFuzzy("A cat sits on a mat", 70);
// With negation
expectlyFuzzy("completely different").not.toMatchFuzzy("hello world");Option 2: Extend Playwright expect with setupExpectlyFuzzy
Call setupExpectlyFuzzy() once in your playwright.config.ts to register toMatchFuzzy on Playwright's native expect globally. Full IntelliSense and type support is included automatically — no per-file imports needed.
// playwright.config.ts
import { setupExpectlyFuzzy } from "@cerios/playwright-expectly-fuzzy";
setupExpectlyFuzzy();Then use expect as usual in your tests:
import { expect, test } from "@playwright/test";
test("AI content validation", async ({ page }) => {
expect("Hello Wrold").toMatchFuzzy("Hello World");
await expect(page.locator("[data-testid='ai-summary']")).toMatchFuzzy("quarterly revenue increase", 75);
});Use alongside setupExpectly() from the core package to get all matchers:
// playwright.config.ts
import { setupExpectly } from "@cerios/playwright-expectly";
import { setupExpectlyFuzzy } from "@cerios/playwright-expectly-fuzzy";
setupExpectly();
setupExpectlyFuzzy();Option 3: Manual expect.extend
For explicit control, extend expect in a shared fixtures file and re-export it:
// tests/fixtures.ts
import { expect, test as base } from "@playwright/test";
import { expectlyFuzzyMatchers } from "@cerios/playwright-expectly-fuzzy";
expect.extend(expectlyFuzzyMatchers);
export { expect };
export const test = base;Then import expect from your fixtures file in each test:
import { expect, test } from "./fixtures";
test("chatbot reply", async ({ page }) => {
expect("Hello Wrold").toMatchFuzzy("Hello World");
await expect(page.locator("[data-testid='bot-reply']")).toMatchFuzzy(
"Your order has been shipped and will arrive in 3 to 5 business days",
70,
);
});Available Matchers
toMatchFuzzy(expected, threshold?)
Asserts that a string or locator's text content fuzzy-matches the expected string using fuzzball's token_sort_ratio algorithm.
| Parameter | Type | Default | Description |
| ----------- | -------- | ------- | ----------------------------------------------------- |
| expected | string | — | The string to compare against |
| threshold | number | 80 | Minimum similarity score (0–100). Pass to be lenient. |
Key behaviours:
- Scores range from 0–100; assertion passes when
score >= threshold - Word-order-insensitive: token sort normalises word order before comparison
- Locator variant polls until the condition is met (or times out)
// String
expectlyFuzzy("Hello Wrold").toMatchFuzzy("Hello World"); // score ~89 ≥ 80 ✓
// Locator (async)
await expectlyFuzzyLocator(page.locator("[data-testid='summary']")).toMatchFuzzy(
"The report shows an increase in quarterly revenue",
75,
);
// Negation
expectlyFuzzy("completely different text").not.toMatchFuzzy("hello world");📖 View full fuzzy matcher docs →
Exports
| Export | Description |
| ------------------------------ | ------------------------------------------------------------ |
| expectlyFuzzy | expect extended with all fuzzy matchers |
| expectlyFuzzyMatchers | Matcher object — pass to expect.extend() |
| expectlyFuzzyString | expect extended with string-only fuzzy matchers |
| expectlyFuzzyStringMatchers | String fuzzy matcher object |
| expectlyFuzzyLocator | expect extended with locator-only fuzzy matchers |
| expectlyFuzzyLocatorMatchers | Locator fuzzy matcher object |
| setupExpectlyFuzzy | Registers all fuzzy matchers on Playwright's global expect |
Usage Examples
Validating AI-generated summaries
import { expectlyFuzzyLocator } from "@cerios/playwright-expectly-fuzzy";
test("AI summary is close enough", async ({ page }) => {
await expectlyFuzzyLocator(page.locator("[data-testid='ai-summary']")).toMatchFuzzy(
"The report shows an increase in quarterly revenue",
75,
);
});Validating chatbot responses
test("chatbot reply is on topic", async ({ page }) => {
await expect(page.locator("[data-testid='bot-reply']")).toMatchFuzzy(
"Your order has been shipped and will arrive in 3 to 5 business days",
70,
);
});Testing with word-order variance
test("word order does not matter", () => {
expectlyFuzzy("revenue quarterly increase report").toMatchFuzzy("quarterly revenue increase report");
});Related Packages
@cerios/playwright-expectly— Core matchers: strings, numbers, dates, arrays, objects, and locators
License
MIT — see LICENSE
