name: e2e-test description: Write a new Playwright E2E test for Vortex. Accepts a plain-text description of what to test, or a Linear issue ID to fetch the description automatically. Uses Chrome DevTools MCP to inspect the live app and source code to produce a well-structured spec. when_to_use: When the user wants to add, scaffold, or write a new E2E test for Vortex. user-invocable: true allowed-tools: mcp__linear-server__list_issues mcp__chrome-devtools__click mcp__chrome-devtools__take_snapshot
E2E Test Writing Skill
You help developers write Playwright E2E tests for Vortex, an Electron desktop app. Tests live in packages/e2e/src/tests/ and MUST follow packages/e2e/E2E-BEST-PRACTICES.md.
Step 1 — Gather scope
If $ARGUMENTS contains a Linear issue ID (pattern: LAZ-\d+ or similar [A-Z]+-\d+):
- Fetch the issue using the
mcp__linear-server__get_issuetool - Extract the title and description to understand what needs to be tested
If $ARGUMENTS is a plain-text description, use it directly.
If no arguments were provided, ask the user: "What should the test cover? Provide a description or a Linear issue ID."
Before proceeding, confirm the scope with the user in one or two sentences: what flows will be tested, which Vortex pages are involved, and whether auth (freeUser/premiumUser) is needed.
Step 2 — Understand the existing code
Read the relevant existing files to understand patterns and available building blocks:
packages/e2e/E2E-BEST-PRACTICES.md— rules you must followpackages/e2e/src/selectors/— existing POMs (check if a relevant one already exists)packages/e2e/src/helpers/— shared utilities (navigation, timeouts, users, etc.)packages/e2e/src/fixtures/vortex-app.ts— available fixtures- Source code under
src/renderer/for UI component structure and text strings
Step 3 — Write a first draft
Follow ALL rules from E2E-BEST-PRACTICES.md. It is fine for selectors and
intermediate steps to be uncertain at this point: the inspector loop in Step 4
exists to discover and correct them against the live app.
At each step whose selector or behavior you are unsure about, insert a breakpoint so the loop can pause there:
import { llmBreakpoint } from "../helpers/inspect";
// ...
await llmBreakpoint(vortexWindow, "after manage game");
llmBreakpoint is a no-op unless VORTEX_E2E_INSPECT is set, so the calls are
safe to leave in the spec while iterating. Strip them once the test is green.
Step 4 — Inspector loop (run, walk, fix, repeat)
The dev inspector runs the real test (fixtures: login snapshot, managed
game, image stubs) headed and single-worker, and launches the app with a fixed
CDP endpoint on 127.0.0.1:9222 that Chrome DevTools MCP attaches to.
Resolve the shared tmp paths the same way llmBreakpoint does (running node
guarantees the shell and the test agree on os.tmpdir() on every platform),
clear any stale files from a prior run, then start the single test you are
authoring. Redirect its output to a log file, and write a done sentinel the
moment the runner exits (in a subshell, so it fires on pass, fail, or crash) so
the poll below can tell a pause apart from an early exit. Run this with
run_in_background so the terminal stays free:
TMP="$(node -e 'console.log(require("os").tmpdir())')"
PAUSE_FILE="$TMP/vortex-e2e-pause"
LOG_FILE="$TMP/vortex-e2e-dev.log"
DONE_FILE="$TMP/vortex-e2e-dev.done"
rm -f "$PAUSE_FILE" "$LOG_FILE" "$DONE_FILE"
( VORTEX_E2E_GREP="<test name>" pnpm nx run @vortex/e2e:dev > "$LOG_FILE" 2>&1; echo $? > "$DONE_FILE" )
VORTEX_E2E_GREP is read by playwright.config.ts as the test grep, so it must
match exactly one test. Passing it as an env var (rather than a trailing CLI
arg) survives forwarding through pnpm and nx cleanly.
When the run reaches an llmBreakpoint it creates $PAUSE_FILE (containing the
label) and blocks indefinitely. But the test can also fail before reaching the
breakpoint (wrong selector, assertion error, launch failure), in which case
$PAUSE_FILE is never created and the runner exits, writing $DONE_FILE. So
the wait must poll for either outcome: pause-file appears, or done-file
appears. Never poll for the pause file alone, it would hang forever on a
pre-breakpoint failure.
TMP="$(node -e 'console.log(require("os").tmpdir())')"
PAUSE_FILE="$TMP/vortex-e2e-pause"
LOG_FILE="$TMP/vortex-e2e-dev.log"
DONE_FILE="$TMP/vortex-e2e-dev.done"
while [ ! -f "$PAUSE_FILE" ] && [ ! -f "$DONE_FILE" ]; do sleep 0.5; done
if [ -f "$PAUSE_FILE" ]; then
echo "[PAUSED] $(cat "$PAUSE_FILE")"
else
echo "[RUNNER EXITED before breakpoint, code $(cat "$DONE_FILE")] read the log to see why it failed:"
tail -n 80 "$LOG_FILE"
fi
Uses only [ -f ], cat, and sleep (no PIDs or signals), so it works on
Linux, macOS, and Git Bash on Windows. If it prints [RUNNER EXITED ...], the
test failed: read the log tail, fix the spec, and re-run from the launch step.
Only drive the MCP when it prints [PAUSED]. (A [E2E-PAUSE] <label> console
line is also emitted but is not guaranteed to reach stdout, so do not grep for
it.)
While paused, drive the live app via the MCP:
list_pages→select_pagefor theindex.htmlwindowtake_snapshotfor the accessibility tree to find roles- Inspect element text, roles, and attributes to pick the best locator
(prefer
getByRole>getByText>getByTitle>getByTestId) click/fillto confirm an action does what the test expects before committing it to the spec
Step forward one breakpoint by resuming via the MCP:
evaluate_script: () => { window.__e2e.resume = true; }
On resume the test removes $PAUSE_FILE, re-arming the watcher. Wait for the
next pause by re-running the same dual-condition poll before driving the app
again (the test may also fail or finish between breakpoints, so still check the
PID, not just the pause file):
TMP="$(node -e 'console.log(require("os").tmpdir())')"
PAUSE_FILE="$TMP/vortex-e2e-pause"
LOG_FILE="$TMP/vortex-e2e-dev.log"
DONE_FILE="$TMP/vortex-e2e-dev.done"
while [ ! -f "$PAUSE_FILE" ] && [ ! -f "$DONE_FILE" ]; do sleep 0.5; done
if [ -f "$PAUSE_FILE" ]; then
echo "[PAUSED] $(cat "$PAUSE_FILE")"
else
echo "[RUNNER EXITED, code $(cat "$DONE_FILE")] no more breakpoints (test passed) or it failed, check the log:"
tail -n 80 "$LOG_FILE"
fi
Walk the whole flow this way. On a wrong selector or failed assertion: stop the
run, edit the spec with what you learned, and re-run. Repeat until the test
passes end to end. MCP click/fill during a pause mutate app state — that is
expected; use it to discover the correct Playwright steps, then bake them in.
NEVER take a screenshot unless the user requests it, always prefer snapshots.
Step 5 — Validate
Remove the llmBreakpoint calls, then run the test normally (headless, as CI
does) to confirm it passes without the inspector:
pnpm -F @vortex/e2e exec playwright test -g "<test name>"