appId: cc.agentlabs.opencode name: Demo mode - offline scripted walkthrough (no server, zero-server activation) --- # Covers the "Try a demo" activation path added for the ~0% 7-day retention # problem: an installer with no self-hosted `opencode serve` server used to # hit a dead end at the no-connection empty state (see activation-positive.yaml # / activation-negative-401.yaml for the "has a server" paths). This flow # never starts a mock opencode server and never taps add-connection-button — # app/demo.tsx (src/lib/demo-script.ts) is fully offline, hardcoded, local # data rendered through the same chat components a real session uses. # # Verifies: the empty-state CTA exists and doesn't disturb the pre-existing # add-connection-button (same testID, same position — activation-positive.yaml # still taps it first), the demo screen renders a scripted conversation # (reasoning -> tool call -> diff -> permission prompt) through the real # MessageBubble/ToolCallCard/DiffView/PermissionPrompt components, the # permission step responds locally (no network — there is no server to talk # to), and the closing CTA actually routes to the real connect form # (app/connection/add.tsx), not a fake/dead link. - launchApp: clearState: true - assertVisible: id: "telemetry-consent-card" - tapOn: id: "telemetry-decline-button" - assertVisible: text: "No Connection" - takeScreenshot: demo-S1_empty_state # add-connection-button must still be the first/only thing tapped here — this # flow only ever taps it in this reachability check (visible, not tapped), # to lock in that its position wasn't disturbed by the new button. - assertVisible: id: "add-connection-button" - tapOn: id: "try-demo-button" - assertVisible: id: "demo-screen" - assertVisible: id: "demo-banner" - takeScreenshot: demo-S2_demo_screen_opened # Scripted conversation renders through the real chat components. # Maestro `text` matches the element's FULL text as a regex, so a phrase # taken from a longer message must be wrapped in `.*…*` (the user message is # a full sentence, not the bare phrase). Verified against a CI run where the # demo screen rendered correctly but this bare-substring assert failed. - assertVisible: id: "chat-bubble-user" - assertVisible: text: ".*login button.*" - assertVisible: id: "chat-bubble-assistant" - assertVisible: text: "Thinking" - takeScreenshot: demo-S3_conversation # Tool call card is collapsed by default (same behavior as a real session) — # tapping it expands the card, which renders the diff DOWNWARD below the # header. Because the card is scrolled to near the bottom of the viewport to # tap it, the expanded diff opens off-screen — so scroll again to bring the # diff into view before asserting (assertVisible does not auto-scroll). # Verified from a CI failure screenshot where the demo rendered correctly but # diff-view-scroll sat below the fold. - scrollUntilVisible: element: text: ".*Edit src/components/LoginButton.tsx.*" direction: DOWN timeout: 10000 - tapOn: text: ".*Edit src/components/LoginButton.tsx.*" - scrollUntilVisible: element: id: "diff-view-scroll" direction: DOWN timeout: 10000 - assertVisible: id: "diff-view-scroll" - takeScreenshot: demo-S4_diff_view # Permission prompt (approve/permission step) — replying only updates local # demo state, never a real sessionClient.permission.reply() network call. - scrollUntilVisible: element: text: "Permission Required" direction: DOWN timeout: 10000 - takeScreenshot: demo-S5_permission_prompt - tapOn: text: "Allow" # Approving swaps the prompt for a completion message that renders below the # fold; scroll to it (full sentence begins "Tests passed — …", matched as a # substring regex). - scrollUntilVisible: element: text: ".*Tests passed.*" direction: DOWN timeout: 10000 - assertVisible: text: ".*Tests passed.*" - takeScreenshot: demo-S6_permission_approved # Closing CTA routes to the REAL connect form, not a dead link. - scrollUntilVisible: element: id: "demo-connect-button" direction: DOWN timeout: 10000 - takeScreenshot: demo-S7_cta - tapOn: id: "demo-connect-button" - assertVisible: id: "connect-ip-input" - takeScreenshot: demo-S8_connect_form_reached