Skip to content

Demo Runner ​

/spexso-demo <issue-id> runs an issue's natural-language demo scenario through a real browser as a developer-facing end-to-end smoke test. It reads the ## Demo > Scenario steps from the issue body and drives them live via a Playwright MCP server — headed, so the developer watches where it strands. On success it optionally saves the run as a Playwright spec; on failure it diagnoses the cause and posts feedback to the issue.

What it does ​

Every spec written with the Spexso template contains a ## Demo section with a ### Scenario: block (numbered natural-language steps) and a ### Reset between demo runs subsection. Until now that section was documentation. /spexso-demo executes it.

The happy-path flow:

  1. Ready the environment (spexso demo-env <id> [--local | --remote [url]]) — the target comes from the flag or the demo config, so a configured project is never asked. Local: probe the app, start it if it is down, wait until it responds. Only a missing config triggers a one-time question, and the answer is saved.
  2. (first run only) Ask for the missing demo values and write them.
  3. Fetch the scenario via spexso issue view <id> (provider-agnostic — works on GitLab, GitHub, and Azure DevOps).
  4. Open the app at baseUrl in a headed browser via the Playwright MCP server.
  5. Run the scenario steps one at a time, applying bounded self-heal (3 retries per step).
  6. On success: print a summary and offer to save the run as tests/demo/issue-<id>.spec.ts. Only written on explicit acceptance.
  7. Stop the app if demo-env started it.

Hybrid model ​

The execution is hybrid:

  • Live, agent-driven — the primary path. The agent reads the natural-language steps and drives a real headed browser through the Playwright MCP server, adapting to minor ambiguity (timing, alternative locators) on the fly.
  • Opt-in persistence — after a successful run, the command offers to save the session as tests/demo/issue-<id>.spec.ts, parameterized by baseUrl. For remote targets, the saved spec loads a captured storageState so it can skip the interactive login. The file is regeneratable on the next successful run; nothing is written on decline or on failure.

The .spec.ts enables manual regression runs but is not wired into CI automatically — see Out of scope.

demo config section ​

Runs read a demo section in .spexso/config.json:

json
{
  "tracker": { "provider": "gitlab", "project": "group/repo" },
  "code":    { "provider": "gitlab", "project": "group/repo" },
  "demo": {
    "localUrl":       "http://localhost:4004",
    "healthCheck":    "http://localhost:4004",
    "startCommand":   "cds watch",
    "readyTimeoutMs": 120000,
    "remoteUrl":      "https://my-app-dev.cfapps.eu10.hana.ondemand.com"
  }
}
FieldRequiredMeaning
localUrlfor localBase URL the demo navigates to for a local run
remoteUrlfor remoteDeployed URL (e.g. DEV) for a remote run — used by a bare --remote, and by default when no localUrl is set
healthChecknoURL probed to decide whether the app is already running; defaults to localUrl
startCommandnoShell command to launch the app when the probe fails (e.g. cds watch, npm start)
readyTimeoutMsnoHow long to wait for healthCheck to respond after starting the app; default 120000 (2 min)

Target selection (in lib/demo-env.js): --remote <url> > bare --remote (→ remoteUrl) > --local > localUrl if set > remoteUrl. If the chosen target is not configured, demo-env returns code: "config"; the command then asks for exactly the missing values once and merges them into the demo section. The section is ignored by all other Spexso commands.

spexso demo-env — the CLI helper ​

lib/demo-env.js provides the deterministic, unit-tested environment resolution. The prose command never probes or starts the app itself — it delegates to this helper and reads the JSON envelope.

bash
spexso demo-env <id>                 # target from config: localUrl, else remoteUrl
spexso demo-env <id> --local         # force local
spexso demo-env <id> --remote        # remote, url from demo.remoteUrl
spexso demo-env <id> --remote <url>  # remote, explicit url (overrides config)

What it does:

  • Local: reads the demo config; probes healthCheck with a short HTTP GET (PROBE_TIMEOUT_MS = 3000 ms).
    • Responds → prints { ok: true, baseUrl, started: false, mode: "local" }.
    • No response + startCommand configured → spawns the app detached, writes its PID to .spexso/demo-<id>.pid, polls healthCheck every POLL_INTERVAL_MS (2 s) until ready or readyTimeoutMs elapses. On ready → { ok: true, baseUrl, started: true, mode: "local" }.
    • No response + no startCommand → { ok: false, code: "config", message: "…" }.
    • Timeout → { ok: false, code: "timeout", message: "…" }.
  • Remote: validates the id and returns { ok: true, baseUrl: <url>, started: false, mode: "remote" } immediately (no url from flag or remoteUrl → code: "config"). Auth handling is the prose command's responsibility.

Envelope shapes:

Success: { ok: true,  baseUrl: string, started: boolean, mode: "local" | "remote" }
Failure: { ok: false, code: "input" | "config" | "timeout", message: string }

The prose reads baseUrl to know where to open the browser, started to know whether to stop the app on cleanup, and ok/code/message to decide whether to proceed or abort.

Local vs remote flow ​

Local ​

spexso demo-env <id>
   probe healthCheck
     ↳ up   → { ok:true, started:false }   (app was already running)
     ↳ down → start app → poll → { ok:true, started:true }
                              or → { ok:false, code:'timeout' }

The command opens the browser at localUrl. No authentication pause is needed for local runs (the app is assumed to be running without an SSO redirect).

Remote ​

spexso demo-env <id> --remote <url>
   → { ok:true, baseUrl:<url>, started:false, mode:'remote' }

The command opens the browser at the remote URL. If the page redirects to an authentication provider (CIS, Azure Entra ID, or other — recognized from the URL/page content), the command pauses and tells the developer to log in manually in the opened browser window. Once the developer confirms login is complete, the command saves the browser storageState to .spexso/demo-artifacts/issue-<id>/storageState.json. Subsequent reruns and any saved .spec.ts load this file to skip the interactive login.

Failure handling ​

Per step the agent applies bounded self-heal: up to 3 retries with a short wait and an alternative locator before treating the failure as genuine. This absorbs routine timing and minor selector noise without masking real regressions.

When self-heal is exhausted:

  1. Stop at the failing step.
  2. Capture artifacts to .spexso/demo-artifacts/issue-<id>/: screenshot, browser console errors, failed network requests, the failing step text, and surrounding DOM context.
  3. Classify the likely cause — one of:
    • app-broken — 5xx response, JS error, or blank page; a real regression.
    • scenario-outdated — the UI no longer matches the step text; the spec drifted.
    • auth-timeout — the auth redirect did not complete in time.
    • selector-drift — element exists but the locator no longer matches after a UI refactor.
    • data-not-reset — precondition data is missing or left over from a previous run.
  4. Local report: print the failing step, the classification, the artifact paths.
  5. Tracker feedback: write the report to a temp file and post it to the issue automatically:
    bash
    npx spexso comment <id> --message-file <report-file>
    Then ask the developer before flagging the issue (defaults to no). Only on explicit confirmation:
    bash
    npx spexso issue update <id> --label "needs::attention"
    needs::attention affects the marathon "Needs attention" board — it is never applied automatically.
  6. Cleanup the started app (via .spexso/demo-<id>.pid), then exit non-zero so the result is scriptable.

The app-broken vs scenario-outdated distinction is the key diagnostic value. A failed demo can mean a real regression or that the written scenario drifted from the current UI after a refactor or redesign. The classification plus the captured screenshot give the developer enough information to tell which — and to route the fix to the right person.

Prerequisites ​

Before running /spexso-demo for the first time:

  1. Install Playwright:

    bash
    npm i -D @playwright/test
    npx playwright install
  2. Enable a Playwright MCP server in your Claude Code session. The exact server package to use depends on your team's setup; any MCP server that exposes a headed Playwright browser to the agent will work.

  3. Add the demo config to .spexso/config.json (see the config section above). The command will prompt for missing values on first run.

If the Playwright MCP server is not connected when the command runs, it stops immediately and prints setup instructions — it does not attempt to run in a degraded mode.

Rules ​

  • Headed by default — the developer watches where it strands.
  • needs::attention is never applied automatically; confirmation is always required.
  • The persisted .spec.ts is opt-in and regeneratable; it is never written on failure or on decline.
  • All tracker interactions go through spexso verbs (issue view, comment, issue update) — never glab, gh, or az directly.
  • spexso demo-env owns starting and stopping the local app; the prose command only triggers it and cleans up via the PID file.

Out of scope ​

  • Visual regression / screenshot diffing.
  • CI integration of the persisted .spec.ts (the file enables this, but wiring it into a pipeline is a later step).
  • Multiple concurrent demos or multi-user choreography in one run.
  • Polished stakeholder-presentation mode (narration, pacing).
  • Non-happy-path / negative scenarios.