Owned tests
Ordinary Playwright, committed by the team and run in their CI. Twinrova is not the test runtime.
A mirror for the suite
Turn real customer sessions into a small set of canonical end-to-end tests.
The recorder is lit. The canon is still being cut.
The gate
Twinrova keeps those walks. When the canon is cut, the walks that repeat become Playwright the team owns. A failing test catches a bug. That is a side effect. The suite is what lets an agent change the product and know the real paths still happen.
Ordinary Playwright, committed by the team and run in their CI. Twinrova is not the test runtime.
The check names the goal. When the clicks change and the goal does not, the how can move. When the goal stops happening, the test stays failed.
A password leaves as a class called secret. The characters typed into the field do not leave with it.
The twin streams
A single script tag writes both. Only one of them becomes the test.
Fire
Written in the live page, at the moment of the action. This is the source of the tests. Mousemove, hover, and scroll are dropped. A burst of keystrokes collapses into one fill.
The locator stack prefers a test id, then role and accessible name, then label. A value is stored as a class: empty, email, secret, text, number, or choice.
Ice
Standard rrweb, with inputs masked, so a person can watch the session that stood for the rest. It is kept for a short window.
The tests still emit if that recording has already expired. The replay is how a human reviews the example. It is not how the suite is generated.
Funnels, heatmaps, and a session player are not the product. The replay is one blob behind the example.
The corridor
Only Record is lit. The rest of the corridor is drawn, and it is not built.
Two streams leave the browser. The action log is kept. The replay expires.
Cut a session on a navigation, an idle gap, or a route change. Template the ids out of the path.
A token of verb, route template, and the best stable locator. Role and name outrank a CSS path.
Exact matches first. Near duplicates next. The model only sees representatives, and only to judge a merge.
The model names the bucket and proposes assertions. The steps come from the medoid trace. The model does not invent them.
A person accepts the assertion. A draft is not part of CI. Merging the spec is the gate.
One Playwright file per accepted run. Recorded emails, cards, and passwords never appear in it.
The vow
Same intent, different locator, or an extra step, and the cohort still reaches the accepted outcome. Open a drift update. A person accepts it.
If checkout no longer reaches confirmation, the test fails. Nothing rewrites the assertion to match the new page.
Rank a use case by how often customers do it. A rare branch that covers many lines is not the path that makes the money.
// canon: checkout-guest-promo
// support: 1204 sessions ยท accepted 2026-10-25
import { test, expect } from "@playwright/test";
test("guest checks out with a promo code", async ({ page }) => {
await page.goto("/cart");
await page.getByRole("button", { name: "Checkout" }).click();
await page.getByLabel("Promo code").fill(process.env.CANON_PROMO_CODE ?? "PROMO");
await page.getByRole("button", { name: "Apply" }).click();
await page.getByRole("button", { name: "Pay now" }).click();
await expect(page.getByRole("heading", { name: "Order confirmed" })).toBeVisible();
}); What is lit
Clustering and Playwright emit are not built. The recorder is. About nine installs in ten are done by an agent, with one config file and no browser. That contract is the plan. These commands run now.
Nothing is uploaded. The log stays in the tab until you copy it.
npm install
npm run snippet Paste the output into the DevTools console, use the site, then:
copy(JSON.stringify(twinrova.dump(), null, 2)) A preview page, and sessions written to .twinrova/sessions/.
npm start
npm run twinrova -- sessions
The preview talks to 127.0.0.1 only. A password field shows up as
valueClass: "secret". The characters do not.
The procedure, including what is never sent, is AGENTS.md.