For Playwright teams with AI agents

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
Node 22.13+MITRuns locallyQuickstart →
~/shop-e2e — zshstandby
❯ 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.

Selectorpage.locator('#btn-submit-2')getByRole('button', { name: 'Log in' })

From .agent-kb/, recorded on /login after actually looking.

Waitingawait 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.

CueCalled whenCommandWhat happens
1Once per reponpx @wiluszdamian/cue initDetects your agents, prints a plan and writes nothing until you say yes.
2Per route, when it changescue survey <url>Records what is really on the page into .agent-kb/, with a date.
3Whenever you want a testAsk your agentIt reads the rules and the map on its own and looks selectors up instead of guessing.
4Every pusheslint . && cue verify --ciA 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/
❯ 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
Eleven rules, ten enforced automatically
  • 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
❯ 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:

PASSPARTIALNOT VERIFIEDFAILEMPTY

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.

TopicDecided by
Page objects, locator strategy, test structure, taggingcue wins
Your app's selectors, routes and vocabularyagent-kb wins
Running, debugging, tracing, reportsplaywright-official
Single scripted browser actionsplaywright-mcp
Accessibility, visual regression, Electronreference-skill
Three lookups over MCP, read-only
  • 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") → cue
  • resolve_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.

Final call

Places, please.

npx @wiluszdamian/cue init

A plan first. Nothing written until you say yes.