@cerios/playwright-expectly-fuzzy
v1.0.0
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
Recommended: a single tests/support module
Create ONE shared module in your own project that extends Playwright's expect and captures the return value, then re-exports it. Every fixture file and spec file imports test/expect from this module — never straight from @playwright/test.
// tests/support/expect.ts
import "@cerios/playwright-expectly-fuzzy";
import { expect as baseExpect, test as base } from "@playwright/test";
import { expectlyFuzzyMatchers } from "@cerios/playwright-expectly-fuzzy";
// MUST capture the return value — never call `.extend()` and discard it.
export const expect = baseExpect.extend(expectlyFuzzyMatchers);
export const test = base;Combining with @cerios/playwright-expectly (or your own matchers) via mergeExpects() — recommended when composing 2+ matcher sources:
// tests/support/expect.ts
import { expect as baseExpect, mergeExpects, test as base } from "@playwright/test";
import { expectlyMatchers } from "@cerios/playwright-expectly";
import { expectlyFuzzyMatchers } from "@cerios/playwright-expectly-fuzzy";
const expectlyExpect = baseExpect.extend(expectlyMatchers);
const fuzzyExpect = baseExpect.extend(expectlyFuzzyMatchers);
export const expect = mergeExpects(baseExpect, expectlyExpect, fuzzyExpect);
export const test = base;If you only need the standalone package exports, the shorter form is also valid:
import { expectly } from "@cerios/playwright-expectly";
import { expectlyFuzzy } from "@cerios/playwright-expectly-fuzzy";
import { expect as baseExpect, mergeExpects, test as base } from "@playwright/test";
export const expect = mergeExpects(baseExpect, expectly, expectlyFuzzy);
export const test = base;Then use expect as usual in your tests:
import { expect, test } from "./support/expect";
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);
});Capturing the return value keeps your setup aligned with Playwright's extended-expect model and composes cleanly when you merge multiple matcher sources. The most visible collision today is in the base package, where the Date toBeCloseTo only exists on the value returned by .extend(...), so using the same returned-value pattern here keeps the combined setup consistent.
Standalone expectlyFuzzy
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");Note: You may see a
setupExpectlyFuzzy()function in older docs or code — it is deprecated. It still works fortoMatchFuzzy(which doesn't collide with any Playwright built-in), but thetests/supportpattern above is the recommended replacement: it correctly captures the return value ofexpect.extend()/mergeExpects(), whichsetupExpectlyFuzzy()does not.
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 baseExpect.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
