testlens-playwright
v0.3.1
Published
Lightweight Playwright reporter that summarizes why tests failed.
Downloads
660
Maintainers
Readme
The problem
Your Playwright test fails in CI. Now what?
You open the logs. You see:
Error: expect(received).toBe(expected)
Expected: "Dashboard"
Received: "Login"Was it a backend API returning 500? A race condition that passes locally? Bad test data hitting a 401? You don't know — and finding out means digging through traces, network logs, and screenshots for 10–30 minutes.
What TestLens does
TestLens adds a single focused summary at the end of every Playwright run that tells you the likely cause of each failure — in plain English, without opening anything.
----------------------------------
TestLens Failure Summary
----------------------------------
FAIL Checkout › Place Order
Likely Cause : Backend Issue
Reason : POST /api/orders returned 500 (3 times)
Confidence : High
FLAKY Search › Filter by category
Likely Cause : Flaky Test
Reason : Passed on retry
Confidence : High
----------------------------------
Summary:
- Total failed tests: 1
- Flaky tests: 1
----------------------------------It automatically classifies each failure as one of:
| Classification | Signal |
|---|---|
| Backend Issue | An API returned >= 500 during the test |
| Client / Test Data Issue | An API returned 4xx during the test |
| Assertion Failure | expect() didn't match — shows expected vs received |
| Flaky Test | Failed on first attempt, passed on retry |
| Test Issue | Timeout, console error, or uncaught page exception |
No configuration. No dashboard. No cloud. Just a clear answer at the bottom of your terminal.
Install & Requirements
- Node.js (modern LTS recommended)
@playwright/test>= 1.40.0 (peer dependency)
Install
npm i -D testlens-playwrightQuickstart
There are two integration paths. Choose the one that matches your project setup.
Path A — Simple suites (specs import directly from @playwright/test)
1) Adopt once
Playwright reporters can't subscribe to page network/console events directly. TestLens bridges that by installing small hooks in a shared test wrapper, then having specs import that wrapper.
Run:
npx testlens-playwright adoptThis creates tests/test.ts and rewrites imports inside tests/ to point to it (with correct relative paths).
If your specs live elsewhere:
npx testlens-playwright adopt --dir e2e2) Enable the reporter
In playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['testlens-playwright/reporter']],
});3) Write tests normally
After adoption, your specs import from the wrapper:
import { test, expect } from './test';
test('example', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/example/i);
});Path B — Custom wrapper (your own test/expect utility)
If your project already has a shared utility like utils/test-base.ts that extends Playwright's test—this is the right path. No spec changes needed.
1) Add TestLens to your wrapper
In your utils/test-base.ts, add attachTestLens and flushTestLens to your existing page fixture:
import { test as base, expect, type Page, type TestInfo } from '@playwright/test';
import { attachTestLens, flushTestLens } from 'testlens-playwright';
const test = base.extend<{}>({
page: async ({ page }, use, testInfo: TestInfo) => {
attachTestLens(page, testInfo); // attach network + console listeners
// ...your existing setup (e.g. ensureGlobalListenersAttached(page))
await use(page);
await flushTestLens(testInfo); // write per-test attachment
},
});
// ...rest of your wrapper (beforeAll, beforeEach, afterAll etc.)
export { test, expect };2) Enable the reporter
In playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['testlens-playwright/reporter']],
});3) Specs stay unchanged
Your specs keep importing exactly as before — no changes required:
import { test } from '../utils/test-base';
import { expect } from '@playwright/test';
test.describe.serial('App checker', () => {
test('to verify user is able to login', async ({ page }) => {
await page.goto('/login');
await expect(page).toHaveTitle('Dashboard');
});
test('to verify user is able to logout', async ({ page }) => {
// ...
});
});
expect: import from@playwright/testdirectly, or re-export it from your wrapper for a single import line. TestLens only needspage+testInfo.
For suites with a shared page in beforeAll
If your suite creates its own context/page in beforeAll (not via the page fixture), attach per-test using beforeEach/afterEach inside your wrapper:
import { test as base, type Page, type BrowserContext, type TestInfo } from '@playwright/test';
import { attachTestLens, flushTestLens } from 'testlens-playwright';
let page: Page;
let context: BrowserContext;
const test = base;
test.beforeAll(async ({ browser }) => {
context = await browser.newContext();
page = await context.newPage();
});
test.beforeEach(async ({}, testInfo: TestInfo) => {
attachTestLens(page, testInfo);
});
test.afterEach(async ({}, testInfo: TestInfo) => {
await flushTestLens(testInfo);
});
test.afterAll(async () => {
await context.close();
});
export { test };How it works
Playwright reporter plugins can't directly subscribe to page events. TestLens uses:
- a small in-test hook that captures signals and attaches a compact JSON summary to each test result
- a reporter that reads those attachments and prints a focused end-of-run summary
- test title + status + retry count + error message
- network aggregates:
{ method, status, url, count }for responses >= 400 - console aggregates:
console.errorandpageerrormessages with counts - URL redaction: querystring/hash stripped before aggregation
CLI
adopt command reference
npx testlens-playwright adopt [--dir <testsDir>] [--wrapper <path>] [--dry-run]--dir: directory containing specs (defaulttests)--wrapper: wrapper path (default<dir>/test.ts)--dry-run: prints what would change (no files written)
Colors
- Default: auto-detected (TTY only)
- Force on:
TESTLENS_COLOR=always - Force off:
TESTLENS_COLOR=never - Respects
NO_COLOR/FORCE_COLOR
Capture scope
- Network: records responses with status >= 400 (by design, to avoid noise)
- URLs: querystring/hash are stripped before aggregation
I don't see network signals
- For Path A: ensure specs import from the wrapper created by
adopt. - For Path B: ensure
attachTestLens(page, testInfo)is called in your wrapper'spagefixture orbeforeEach. - If your tests are not under
tests/, re-run adoption with--dir.
I only see console/pageerror reasons
Often the test failed before any API calls were made, or the failure isn't represented by an HTTP response (DNS issues, request never left, etc.). MVP focuses on HTTP status + console/page errors.
Colors look weird in CI
- Set
TESTLENS_COLOR=neverorNO_COLOR=1 - Or force colors with
TESTLENS_COLOR=alwaysif your CI supports it
Development
npm testnpm run test:integration- No UI dashboard
- No database / cloud sync
- No auth
