e2e-codegen
Playwright E2E Codegen
Turns "I ran this conversationally and it passed" into "this is now a committed test file CI runs on its own." This is the one skill in the toolkit whose output outlives the conversation — everything else here reports on a session; this one produces code.
Relationship to the other skills
Input is a verified scenario — one flow-runner has already executed and confirmed passes, or a mocked negative case network-assertion confirmed behaves correctly, or a matrix cell cross-browser-matrix confirmed holds across engines/viewports. Not a raw scenario-mapper row — that's a hypothesis about what's worth testing, not a confirmed fact about how the site behaves. Generating code straight from an unverified hypothesis just moves an unchecked assumption into a form that reads as more trustworthy than it is. If asked to codify something that hasn't been run yet, verify it first.
Output feeds test-plan's coverage ledger — once a test is generated and passing, that ledger's "Automated (e2e-codegen)?" column should reflect it. A ledger that isn't updated when this skill runs is worse than no ledger, since it actively misrepresents coverage.
Core principles (and why)
Every step becomes a real assertion, not just an action. Same discipline as flow-runner, now expressed permanently in code: a generated test that's all clicks with no expect() calls is a flake detector at best, not a correctness check. Translate each step's expected outcome from the source scenario directly into an assertion — don't just replay the clicks.
Use resilient, role-based locators. getByRole, getByLabel, getByText — not CSS selectors tied to a class name or DOM position. This is the generated-code version of "read the accessibility tree, don't guess coordinates," which has been a principle since flow-runner; it matters more here because code that breaks on every unrelated markup tweak is exactly the kind of test a team stops trusting and starts ignoring.
Never emit a fixed-time wait. Use Playwright's auto-waiting assertions (expect(locator).toBeVisible(), toHaveText(), etc.), which retry until the condition holds or a timeout elapses — never page.waitForTimeout(2000). Same "wait for state, not time" reasoning as everywhere else in this toolkit, except a flaky fixed wait baked into committed code causes real, recurring CI pain rather than a one-off flake.
Reuse auth state through a shared fixture — don't repeat a login flow in every generated file. If multiple generated tests need to start authenticated, generate (or reuse) one setup that saves storageState once and have each test load it. Mirrors flow-runner's reasoning about not re-logging-in per scenario, now as actual shared code instead of a repeated instruction.