Your agent knows Playwright. It doesn't know your app.
Cue gives it its cues: your team's testing rules as ESLint rules it can't talk past, and a map of the routes and selectors your application really has.
npx @wiluszdamian/cue init❯ npx @wiluszdamian/cue init Detected: claude-code, cursor create AGENTS.md create eslint.config.mjs create .agent-kb/ skip playwright.config.ts yours, left alone ? Apply this plan? yes ❯ cue survey http://localhost:3000/login ✓ /login 4 elements recorded button getByRole('button', { name: 'Log in' }) ❯ npx eslint tests/login.spec.ts 14:3 error no-hard-waits waitForTimeout waits on the clock, not on the app. Use await expect(locator).toBeVisible() ✗ 1 problem · agent rewrites the wait ✓ 0 problems
A reasonable test, for some other project.
Ask an agent for a Playwright test and it writes what it has seen a thousand times. It invents selectors your app doesn't have and skips the conventions your team agreed on, because it has never seen your repo or your application.
page.locator('#btn-submit-2')getByRole('button', { name: 'Log in' })From .agent-kb/, recorded on /login after actually looking.
await page.waitForTimeout(3000)await expect(alert).toBeVisible()From no-hard-waits, enforced by ESLint on every save and push.
The running order
Four cues from an untouched repo to a test your team would sign off. The last one runs in CI with no agent in the room.
| Cue | Called when | Command | What happens |
|---|---|---|---|
| 1 | Once per repo | npx @wiluszdamian/cue init | Detects your agents, prints a plan and writes nothing until you say yes. |
| 2 | Per route, when it changes | cue survey <url> | Records what is really on the page into .agent-kb/, with a date. |
| 3 | Whenever you want a test | Ask your agent | It reads the rules and the map on its own and looks selectors up instead of guessing. |
| 4 | Every push | eslint . && cue verify --ci | A broken rule fails the build. Map drift fails the check. |
Rules your agent can't talk past
Your conventions ship as real ESLint rules. Every message says what is wrong, why, and what to write instead, because "violates rule" teaches an agent to route around it and naming the replacement teaches it to fix the test.
❯ npx eslint tests/ tests/checkout.spec.ts 14:3 error no-hard-waits waitForTimeout waits on the clock, not on the app. Wait on the state you actually care about with a web-first assertion, which retries until the condition holds: await expect(locator).toBeVisible(), .toHaveText(), .toBeEnabled(). For a network round-trip, use await page.waitForResponse(...). ✗ 1 problem
- no-hard-waitsenforced
- web-first-assertionsenforced
- no-raw-selectorsenforced
- no-locators-in-testsenforced
- strict-zod-objectsenforced
- no-focused-testsenforced
- skips-need-a-reasonenforced
- require-test-tagsenforced
- no-explicit-anyenforced
- no-hardcoded-urlsenforced
- selectors-from-agent-kbreview only
They live in one YAML file. Change it and the ESLint rules, the docs and the agent instructions update together. cue explain no-hard-waits prints the reasoning for any rule.
A map of the app, with a date on every entry
Cue writes down what is on each page after actually looking, and cross-checks it against your source. Every entry can answer one question: why does Cue believe this?
❯ cue locator "Log in" --route /login Route: /login Locator: getByRole('button', { name: 'Log in' }) Test id: login-submit Confidence: confirmed Present in the product source and seen running. Freshness: fresh
Confidence says whether a locator was confirmed in source and at runtime, seen only at runtime, or found only in code. The agent knows which ones may move.
Unknown means unknown. With no entry, Cue says so and names the command that fills the gap. It never hands back a plausible guess.
Verify reports exactly what it checked against the running app:
Cue doesn't teach Playwright. It decides who does.
Your agent may load Microsoft's Playwright skills, Playwright MCP and a community reference skill. None of them knows the others exist. Cue installs them and adds one table that says which source wins on each topic.
| Topic | Decided by |
|---|---|
| Page objects, locator strategy, test structure, tagging | cue wins |
| Your app's selectors, routes and vocabulary | agent-kb wins |
| Running, debugging, tracing, reports | playwright-official |
| Single scripted browser actions | playwright-mcp |
| Accessibility, visual regression, Electron | reference-skill |
explain_ruleWhy a rule exists and what to write instead.explain_rule("no-hard-waits")resolve_ownerWhich source wins on a topic.resolve_owner("test data strategy") → cueresolve_locatorA real selector, with confidence and freshness.resolve_locator("Log in", "/login")
Your repo stays yours
Running init twice changes nothing
Every write goes through a plan you can read first.
Your edits are never overwritten
A file you changed by hand is reported and left alone. Existing files are touched only inside a marked region.
Uninstall is exact
Removal reverses what was installed from a recorded manifest, not from guesswork.
Does it make agents write better tests? Not measured yet.
Cue includes a harness that records and scores agent runs. No benchmark has been run, so we don't claim better tests. What you can check today is the mechanism: rules that fail the build, and selectors verified against the running app. The numbers will go here when they exist, whatever they say.
Questions
Do I need an AI agent to use it?
No. The ESLint rules work on their own, in CI and in your editor, and catch the same mistakes from people.
Which agents does it set up?
Claude Code, Cursor, Codex, Grok, Gemini CLI and OpenCode. Every agent reads AGENTS.md, so the baseline applies even to one Cue doesn't detect.
Does it work on an existing, messy suite?
That's the main target. Cue leaves files it doesn't own alone, and the map is useful long before the whole app is surveyed.
Why the long package name?
Always install the scoped @wiluszdamian/cue. An unscoped name run through npx can fetch an unrelated package. Once installed, the short cue command is enough.
Does anything leave my machine?
No. The CLI, the ESLint plugin, the map and the MCP server all run locally. There is no hosted service.
Places, please.
npx @wiluszdamian/cue initA plan first. Nothing written until you say yes.