codebyplan 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +602 -345
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/agents/cbp-cc-executor.md +1 -1
- package/templates/agents/cbp-e2e-maestro.md +202 -0
- package/templates/agents/cbp-e2e-playwright.md +229 -0
- package/templates/agents/cbp-e2e-tauri.md +184 -0
- package/templates/agents/cbp-e2e-vscode.md +203 -0
- package/templates/agents/cbp-e2e-xcuitest.md +224 -0
- package/templates/agents/cbp-improve-claude.md +1 -1
- package/templates/agents/cbp-round-executor.md +11 -11
- package/templates/agents/cbp-task-check.md +1 -1
- package/templates/agents/cbp-task-planner.md +2 -0
- package/templates/agents/cbp-testing-qa-agent.md +9 -9
- package/templates/context/testing/e2e.md +303 -0
- package/templates/hooks/cbp-statusline.mjs +44 -0
- package/templates/hooks/cbp-statusline.py +24 -2
- package/templates/hooks/cbp-statusline.sh +22 -2
- package/templates/hooks/validate-structure-lengths.sh +2 -0
- package/templates/hooks/validate-structure-smoke.sh +2 -1
- package/templates/hooks/validate-structure-templates.sh +1 -0
- package/templates/rules/README.md +8 -1
- package/templates/rules/context-file-loading.md +4 -1
- package/templates/rules/e2e-mandatory.md +70 -0
- package/templates/rules/supabase-branch-lifecycle.md +99 -0
- package/templates/settings.project.base.json +1 -2
- package/templates/skills/cbp-build-cc-agent/SKILL.md +16 -14
- package/templates/skills/cbp-build-cc-agent/reference/cbp-quality.md +4 -4
- package/templates/skills/cbp-build-cc-agent/scripts/validate-agent.sh +8 -6
- package/templates/skills/cbp-build-cc-mode/SKILL.md +4 -4
- package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +1 -2
- package/templates/skills/cbp-checkpoint-check/SKILL.md +12 -8
- package/templates/skills/cbp-checkpoint-create/SKILL.md +2 -0
- package/templates/skills/cbp-checkpoint-end/SKILL.md +27 -5
- package/templates/skills/cbp-checkpoint-plan/SKILL.md +2 -2
- package/templates/skills/cbp-checkpoint-plan/reference/e2e-discovery-probe.md +5 -5
- package/templates/skills/cbp-e2e-setup/SKILL.md +254 -0
- package/templates/skills/cbp-e2e-setup/reference/maestro.md +200 -0
- package/templates/skills/cbp-e2e-setup/reference/playwright.md +212 -0
- package/templates/skills/cbp-e2e-setup/reference/tauri.md +147 -0
- package/templates/skills/cbp-e2e-setup/reference/vscode.md +154 -0
- package/templates/skills/cbp-e2e-setup/reference/xcuitest.md +185 -0
- package/templates/skills/cbp-frontend-ui/SKILL.md +6 -6
- package/templates/skills/cbp-frontend-ux/SKILL.md +1 -1
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +17 -1
- package/templates/skills/cbp-round-execute/SKILL.md +30 -17
- package/templates/skills/cbp-session-start/SKILL.md +27 -2
- package/templates/skills/cbp-ship-main/SKILL.md +13 -0
- package/templates/skills/cbp-supabase-branch-check/SKILL.md +12 -5
- package/templates/skills/cbp-supabase-migrate/SKILL.md +139 -9
- package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +1 -1
- package/templates/skills/cbp-supabase-setup/SKILL.md +13 -7
- package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +2 -2
- package/templates/skills/cbp-task-check/SKILL.md +2 -2
- package/templates/skills/cbp-task-start/SKILL.md +2 -0
- package/templates/agents/cbp-test-e2e-agent.md +0 -363
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
scope: org-shared
|
|
3
|
+
name: cbp-e2e-setup
|
|
4
|
+
description: Detect installed E2E frameworks, ask which to enable, record credentials source (gitignored env-file path + var names only, never secrets), and write/refresh .codebyplan/e2e.json. Interactive, idempotent.
|
|
5
|
+
argument-hint: "[--force]"
|
|
6
|
+
model: sonnet
|
|
7
|
+
effort: xhigh
|
|
8
|
+
allowed-tools: Read, Write, Edit, Bash(cat *), Bash(jq *), Bash(which *), Bash(test *), Bash(mkdir *), Bash(cp *), Bash(echo *), Bash(date *), Bash(mv *), Bash(git check-ignore *), AskUserQuestion, mcp__codebyplan__get_repos
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# E2E Setup
|
|
12
|
+
|
|
13
|
+
Configure `.codebyplan/e2e.json` so the E2E test pipeline knows which frameworks are
|
|
14
|
+
enabled, where each app lives, and where to read credentials at test time.
|
|
15
|
+
|
|
16
|
+
Invoke at any time. Already-configured frameworks are preserved unless `--force` is passed.
|
|
17
|
+
Pass `--force` to re-ask all questions including credentials blocks.
|
|
18
|
+
|
|
19
|
+
## Arguments
|
|
20
|
+
|
|
21
|
+
Inspect `$ARGUMENTS` for `--force`. If present, set `force_mode = true`.
|
|
22
|
+
Absent: use idempotent mode — preserve existing credentials blocks, skip re-asking
|
|
23
|
+
already-configured frameworks.
|
|
24
|
+
|
|
25
|
+
## Step 1 — Detect installed frameworks
|
|
26
|
+
|
|
27
|
+
Run both detection signals and merge:
|
|
28
|
+
|
|
29
|
+
**Signal A — DB tech_stack** via `mcp__codebyplan__get_repos` (match `repo_id` from
|
|
30
|
+
`.codebyplan/repo.json`). Scan `tech_stack[]` for: `playwright`, `maestro`, `xcuitest`,
|
|
31
|
+
`webdriverio`, `@wdio/cli`, `@vscode/test-cli`.
|
|
32
|
+
|
|
33
|
+
**Signal B — Filesystem probes:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
test -f playwright.config.ts || test -f playwright.config.js # → playwright
|
|
37
|
+
test -f maestro/config.yaml || test -d maestro # → maestro
|
|
38
|
+
test -d ios && find ios -name '*UITests' -maxdepth 2 | grep -q . # → xcuitest
|
|
39
|
+
test -f wdio.conf.ts || test -f wdio.conf.js # → tauri (wdio)
|
|
40
|
+
test -f .vscode-test.mjs || test -d apps/vscode # → vscode
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Signal C — Read existing `.codebyplan/e2e.json`** for idempotent merge:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cat .codebyplan/e2e.json 2>/dev/null || echo '{}'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A framework detected by A or B is "detected". A framework already in e2e.json is
|
|
50
|
+
"configured". A framework with `enabled: false` in e2e.json is "configured-disabled".
|
|
51
|
+
|
|
52
|
+
## Step 2 — Ask which to enable
|
|
53
|
+
|
|
54
|
+
Display a summary table of detection results:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Framework | Detected | Configured | Status
|
|
58
|
+
----------- | -------- | ---------- | ------
|
|
59
|
+
playwright | yes | yes | enabled
|
|
60
|
+
maestro | no | no | absent
|
|
61
|
+
xcuitest | no | no | absent
|
|
62
|
+
tauri | no | no | absent
|
|
63
|
+
vscode | no | no | absent
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
AskUserQuestion (multi-select):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Which E2E frameworks should be enabled?
|
|
70
|
+
Detected frameworks are pre-checked. Undetected ones can still be enabled.
|
|
71
|
+
|
|
72
|
+
Select all that apply:
|
|
73
|
+
A) playwright (web — Next.js)
|
|
74
|
+
B) maestro (mobile — Expo/React Native)
|
|
75
|
+
C) xcuitest (iOS native — Apple Watch, HealthKit, system dialogs)
|
|
76
|
+
D) tauri (desktop — WebDriverIO + tauri-driver)
|
|
77
|
+
E) vscode (VS Code extension — @vscode/test-cli)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In `--force` mode: re-ask even for frameworks already enabled.
|
|
81
|
+
Otherwise: frameworks already `enabled: true` in e2e.json are kept without asking.
|
|
82
|
+
|
|
83
|
+
## Step 3 — Mobile platforms (conditional)
|
|
84
|
+
|
|
85
|
+
If maestro or xcuitest is in the enabled set and the framework is not yet configured
|
|
86
|
+
(or `--force`), AskUserQuestion:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Mobile platform target for <framework>:
|
|
90
|
+
A) Android only
|
|
91
|
+
B) iOS only
|
|
92
|
+
C) Both Android and iOS
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Record the answer as `platforms: ["android"]`, `["ios"]`, or `["android", "ios"]` on the
|
|
96
|
+
framework config block.
|
|
97
|
+
|
|
98
|
+
## Step 4 — Credentials source
|
|
99
|
+
|
|
100
|
+
For each enabled framework that touches auth (playwright, maestro, xcuitest):
|
|
101
|
+
|
|
102
|
+
**Idempotency gate** (skip if `--force` is absent AND
|
|
103
|
+
`credentials.frameworks[framework].email_var` AND
|
|
104
|
+
`credentials.frameworks[framework].password_var` are both set to non-empty strings —
|
|
105
|
+
an empty `{}` entry counts as unconfigured, so prompt for it):
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Credentials block for <framework> already configured — use --force to reset.
|
|
109
|
+
env_file: <path>
|
|
110
|
+
email_var: <var>
|
|
111
|
+
password_var: <var>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Print the preserved values and continue to Step 5.
|
|
115
|
+
|
|
116
|
+
**Otherwise**, AskUserQuestion (one question per framework, step-by-step):
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Credentials source for <framework>
|
|
120
|
+
|
|
121
|
+
1. Gitignored env-file path that holds the secrets
|
|
122
|
+
(default: .codebyplan/e2e.env — a dedicated file, separate from app .env.local)
|
|
123
|
+
Path: ___
|
|
124
|
+
|
|
125
|
+
2. Email env var name (default: E2E_TEST_EMAIL for playwright, TEST_EMAIL for others)
|
|
126
|
+
Var name: ___
|
|
127
|
+
|
|
128
|
+
3. Password env var name (default: E2E_TEST_PASSWORD for playwright, TEST_PASSWORD for others)
|
|
129
|
+
Var name: ___
|
|
130
|
+
|
|
131
|
+
4. (Optional) Provision script path — the skill only records the path, never creates it
|
|
132
|
+
(convention: scripts/provision-e2e-user.ts — leave blank to skip)
|
|
133
|
+
Path: ___
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
After collecting the env-file path, verify it is gitignored and capture the result —
|
|
137
|
+
this boolean is persisted as the required `credentials.gitignored` field:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git check-ignore -q <env_file_path> && GITIGNORED=true || GITIGNORED=false
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If NOT ignored (`GITIGNORED=false`): warn and offer to append it:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Warning: <path> is not in .gitignore.
|
|
147
|
+
This file will contain live credentials — committing it is a credential leak.
|
|
148
|
+
Append <path> to .gitignore? (Y/n)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
On yes: append the path to `.gitignore` and set `GITIGNORED=true`. On no: leave
|
|
152
|
+
`GITIGNORED=false` and record in the output but do not block.
|
|
153
|
+
|
|
154
|
+
Carry `gitignored: $GITIGNORED` into the credentials block assembled for Step 5 — the
|
|
155
|
+
`E2eCredentials.gitignored` field is required, so it must always be populated.
|
|
156
|
+
|
|
157
|
+
Never write secret values into e2e.json — only the path, var names, and provision_script
|
|
158
|
+
reference are persisted.
|
|
159
|
+
|
|
160
|
+
## Step 5 — Write .codebyplan/e2e.json
|
|
161
|
+
|
|
162
|
+
Build the updated payload conforming to the `E2eConfig` schema
|
|
163
|
+
(`packages/codebyplan-package/src/lib/types.ts`).
|
|
164
|
+
|
|
165
|
+
For playwright, derive `base_url` from `.codebyplan/server.json`:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
jq -r '.port_allocations[] | select(.label == "Web Dev") | "http://localhost:\(.port)"' \
|
|
169
|
+
.codebyplan/server.json | head -1
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Match by the `Web Dev` label rather than `server_type == "nextjs"` — a repo can have
|
|
173
|
+
several `nextjs` allocations (e.g. one per sibling worktree), so array-position
|
|
174
|
+
`head -1` is not stable. Confirm the derived URL with the user before writing
|
|
175
|
+
(`Derived base_url: <url> — correct? (Y/n)`) and allow an override. Store as
|
|
176
|
+
`frameworks.playwright.base_url`.
|
|
177
|
+
|
|
178
|
+
Idempotency rule: the jq object-merge (`. + {...}`) REPLACES top-level keys, so build
|
|
179
|
+
`$CREDENTIALS_JSON` as a deep-merge of the existing block and the newly-collected data
|
|
180
|
+
BEFORE the write — otherwise a second run for an additional framework would clobber the
|
|
181
|
+
first framework's credentials. Assemble it in the shell:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
EXISTING_CREDS=$(jq -c '.credentials // {}' .codebyplan/e2e.json)
|
|
185
|
+
CREDENTIALS_JSON=$(echo "$EXISTING_CREDS" | jq -c --argjson new "$NEW_CREDS_JSON" '. * $new')
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The `*` operator deep-merges, so frameworks skipped by the idempotency gate keep their
|
|
189
|
+
prior entry. Then write atomically using jq temp+mv to avoid partial writes (only the two
|
|
190
|
+
schema fields `frameworks` and `credentials` are written — `E2eConfig` defines no other):
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
jq --argjson frameworks "$FRAMEWORKS_JSON" \
|
|
194
|
+
--argjson credentials "$CREDENTIALS_JSON" \
|
|
195
|
+
'. + {frameworks: $frameworks, credentials: $credentials}' \
|
|
196
|
+
.codebyplan/e2e.json > .codebyplan/e2e.json.tmp \
|
|
197
|
+
&& mv .codebyplan/e2e.json.tmp .codebyplan/e2e.json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Framework config shape per framework:
|
|
201
|
+
|
|
202
|
+
| Field | playwright | maestro / xcuitest | tauri / vscode |
|
|
203
|
+
| ------------ | ------------------- | -------------------- | -------------- |
|
|
204
|
+
| `enabled` | true | true | true |
|
|
205
|
+
| `app` | `apps/web` (nextjs) | `apps/<expo-app>` | `apps/desktop` / `apps/vscode` |
|
|
206
|
+
| `config_path`| `playwright.config.ts` | `maestro/config.yaml` | `wdio.conf.ts` / `.vscode-test.mjs` |
|
|
207
|
+
| `auto_run` | false | false | false |
|
|
208
|
+
| `test_dir` | `apps/web/e2e` | — | — |
|
|
209
|
+
| `base_url` | from server.json | — | — |
|
|
210
|
+
| `platforms` | — | from Step 3 | — |
|
|
211
|
+
|
|
212
|
+
Disabled frameworks get `enabled: false`; all other fields are preserved from their
|
|
213
|
+
prior configured state.
|
|
214
|
+
|
|
215
|
+
## Step 6 — Verify and report
|
|
216
|
+
|
|
217
|
+
Re-read `.codebyplan/e2e.json` and emit a per-framework summary:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
E2E Setup — Complete
|
|
221
|
+
|
|
222
|
+
Framework | Status | App | base_url / platforms | Creds source
|
|
223
|
+
----------- | -------- | ---------- | --------------------- | ------------
|
|
224
|
+
playwright | enabled | apps/web | http://localhost:3010 | .codebyplan/e2e.env (E2E_TEST_EMAIL)
|
|
225
|
+
maestro | disabled | — | — | —
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
e2e.json written to .codebyplan/e2e.json
|
|
229
|
+
|
|
230
|
+
Next steps per framework — see reference docs:
|
|
231
|
+
playwright → reference/playwright.md
|
|
232
|
+
maestro → reference/maestro.md
|
|
233
|
+
xcuitest → reference/xcuitest.md
|
|
234
|
+
tauri → reference/tauri.md
|
|
235
|
+
vscode → reference/vscode.md
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Key Rules
|
|
239
|
+
|
|
240
|
+
- Never write secret values into e2e.json — only env-file path + var names
|
|
241
|
+
- Gitignore guard runs before any credentials are persisted
|
|
242
|
+
- Preserved credentials blocks are printed verbatim so the user can verify them
|
|
243
|
+
- Atomic write (tmp + mv) — never leaves e2e.json in a partial state
|
|
244
|
+
- `auto_run: false` by default — the user opts in explicitly
|
|
245
|
+
|
|
246
|
+
## Additional resources
|
|
247
|
+
|
|
248
|
+
- Playwright install + auth + CI: [reference/playwright.md](reference/playwright.md)
|
|
249
|
+
- Maestro install + flows + CI: [reference/maestro.md](reference/maestro.md)
|
|
250
|
+
- Tauri (WebDriverIO): [reference/tauri.md](reference/tauri.md)
|
|
251
|
+
- VS Code extension testing: [reference/vscode.md](reference/vscode.md)
|
|
252
|
+
- XCUITest (iOS native): [reference/xcuitest.md](reference/xcuitest.md)
|
|
253
|
+
- E2E schema types: `packages/codebyplan-package/src/lib/types.ts` (E2eConfig)
|
|
254
|
+
- Shared E2E conventions: `.claude/context/testing/e2e.md`
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Maestro Reference
|
|
2
|
+
|
|
3
|
+
Full install, config, flows, and CI walkthrough for Maestro on an Expo/React Native project.
|
|
4
|
+
Source: vendor/maestro/v2.6 + `.claude/context/testing/e2e.md`.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- Java 17 or later: `java -version` (install via `brew install openjdk@17` on macOS)
|
|
9
|
+
- Android emulator (for android targets) or iOS Simulator (for ios targets)
|
|
10
|
+
- Expo app bundled and running on the target device/emulator
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# macOS — recommended
|
|
16
|
+
curl -fsSL "https://get.maestro.mobile.dev" | bash
|
|
17
|
+
|
|
18
|
+
# Alternative: Homebrew tap
|
|
19
|
+
brew tap mobile-dev-inc/tap
|
|
20
|
+
brew install maestro
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Verify:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
maestro --version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Update later with: `maestro upgrade`
|
|
30
|
+
|
|
31
|
+
## maestro/config.yaml
|
|
32
|
+
|
|
33
|
+
Create at repo root under `maestro/config.yaml`:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# Maestro workspace configuration
|
|
37
|
+
# See: https://docs.maestro.dev/maestro-flows/workspace-management/repository-configuration
|
|
38
|
+
|
|
39
|
+
appId: com.yourorg.yourapp # must match app.config.ts / expo config
|
|
40
|
+
env:
|
|
41
|
+
TEST_EMAIL: ${TEST_EMAIL}
|
|
42
|
+
TEST_PASSWORD: ${TEST_PASSWORD}
|
|
43
|
+
APP_ID: com.yourorg.yourapp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`appId` must match the value in `app.config.ts` `ios.bundleIdentifier` /
|
|
47
|
+
`android.package`. If they differ across platforms, use the Android package ID for
|
|
48
|
+
Maestro's `appId` on Android and the iOS bundle ID on iOS tests.
|
|
49
|
+
|
|
50
|
+
## Shared login flow
|
|
51
|
+
|
|
52
|
+
Create `maestro/flows/_shared/login.yaml`:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
appId: ${APP_ID}
|
|
56
|
+
---
|
|
57
|
+
- launchApp:
|
|
58
|
+
clearState: true
|
|
59
|
+
- assertVisible: "Sign in"
|
|
60
|
+
- tapOn: "Email"
|
|
61
|
+
- inputText: ${TEST_EMAIL}
|
|
62
|
+
- tapOn: "Password"
|
|
63
|
+
- inputText: ${TEST_PASSWORD}
|
|
64
|
+
- tapOn: "Sign in"
|
|
65
|
+
- assertVisible:
|
|
66
|
+
text: ".*" # Replace with a post-login element (e.g. "Dashboard")
|
|
67
|
+
timeout: 15000
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Reference it from any other flow:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- runFlow: _shared/login.yaml
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Auth probe flow
|
|
77
|
+
|
|
78
|
+
`maestro/flows/_probe/auth.yaml` — minimal login verification:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
appId: ${APP_ID}
|
|
82
|
+
tags:
|
|
83
|
+
- probe
|
|
84
|
+
---
|
|
85
|
+
- launchApp:
|
|
86
|
+
clearState: true
|
|
87
|
+
- assertVisible: "Sign in"
|
|
88
|
+
- tapOn: "Email"
|
|
89
|
+
- inputText: ${TEST_EMAIL}
|
|
90
|
+
- tapOn: "Password"
|
|
91
|
+
- inputText: ${TEST_PASSWORD}
|
|
92
|
+
- tapOn: "Sign in"
|
|
93
|
+
- assertVisible:
|
|
94
|
+
text: ".*"
|
|
95
|
+
timeout: 15000
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Run the probe before the full suite: `maestro test maestro/flows/_probe/auth.yaml`
|
|
99
|
+
|
|
100
|
+
## Platform targeting
|
|
101
|
+
|
|
102
|
+
Maestro v2.6 exposes `-p` / `--platform` as a global option (placed BEFORE the `test`
|
|
103
|
+
subcommand). Values: `android`, `ios`, or `web`.
|
|
104
|
+
|
|
105
|
+
Run on Android: start an Android emulator, then
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
maestro --platform=android test maestro/flows/
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Run on iOS: boot an iOS Simulator, then
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
maestro --platform=ios test maestro/flows/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Omitting the flag is also valid — platform is then implicit from whichever single
|
|
118
|
+
emulator/simulator is currently running.
|
|
119
|
+
|
|
120
|
+
Target a specific device by UDID / emulator name:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
maestro test --device <device-id> maestro/flows/
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Directory structure
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
maestro/
|
|
130
|
+
config.yaml
|
|
131
|
+
flows/
|
|
132
|
+
_shared/
|
|
133
|
+
login.yaml
|
|
134
|
+
open-side-menu.yaml
|
|
135
|
+
_probe/
|
|
136
|
+
auth.yaml
|
|
137
|
+
onboarding/
|
|
138
|
+
signup.yaml
|
|
139
|
+
home/
|
|
140
|
+
dashboard.yaml
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
One subdirectory per app module under `maestro/flows/`. Shared flows under `_shared/`.
|
|
144
|
+
|
|
145
|
+
## Screenshots
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
- takeScreenshot: "after-login"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Configure a repo-local screenshots path in `maestro/config.yaml`:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
screenshotsDir: maestro/screenshots
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## pnpm scripts
|
|
158
|
+
|
|
159
|
+
Add to root `package.json`:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"scripts": {
|
|
164
|
+
"maestro:test": "maestro test maestro/flows/",
|
|
165
|
+
"maestro:test:probe": "maestro test maestro/flows/_probe/",
|
|
166
|
+
"maestro:studio": "maestro studio"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## CI (GitHub Actions)
|
|
172
|
+
|
|
173
|
+
Maestro CI runs require a connected device. For GitHub Actions use
|
|
174
|
+
[Maestro Cloud](https://cloud.maestro.dev) or a self-hosted runner with a connected
|
|
175
|
+
device. A minimal Maestro Cloud step:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
- name: Run Maestro flows
|
|
179
|
+
uses: mobile-dev-inc/action-maestro-cloud@v1
|
|
180
|
+
with:
|
|
181
|
+
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
|
182
|
+
app-file: path/to/app.apk # or .ipa
|
|
183
|
+
flow-file: maestro/flows/
|
|
184
|
+
env:
|
|
185
|
+
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
|
186
|
+
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For local self-hosted runner, set `TEST_EMAIL` and `TEST_PASSWORD` as runner env vars.
|
|
190
|
+
|
|
191
|
+
## Pitfalls
|
|
192
|
+
|
|
193
|
+
**App ID mismatch** — `appId` in config.yaml must exactly match the compiled bundle
|
|
194
|
+
identifier. Re-run `expo prebuild` if you changed the identifier after prebuild.
|
|
195
|
+
|
|
196
|
+
**clearState: true** — always clear app state in `launchApp` for the login flow so
|
|
197
|
+
each run starts from a signed-out state.
|
|
198
|
+
|
|
199
|
+
**Java version** — Maestro requires Java 17+. If `maestro --version` fails, check
|
|
200
|
+
`JAVA_HOME` or install via Homebrew.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Playwright Reference
|
|
2
|
+
|
|
3
|
+
Full install, config, auth, and CI walkthrough for Playwright on a Next.js monorepo.
|
|
4
|
+
Source: vendor/playwright/v1.60 + `.claude/context/testing/e2e.md`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add -D @playwright/test
|
|
10
|
+
pnpm exec playwright install chromium
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For CI with system dependencies:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm exec playwright install --with-deps chromium
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## playwright.config.ts
|
|
20
|
+
|
|
21
|
+
Derive `baseURL` from `.codebyplan/server.json` at config-read time:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
25
|
+
import { execSync } from "child_process";
|
|
26
|
+
import path from "path";
|
|
27
|
+
|
|
28
|
+
// Pull the Web Dev port from server.json so config stays in sync. Match by label
|
|
29
|
+
// rather than server_type — a repo can have several nextjs allocations, so
|
|
30
|
+
// array-position head -1 is not stable.
|
|
31
|
+
function getBaseUrl(): string {
|
|
32
|
+
try {
|
|
33
|
+
const raw = execSync(
|
|
34
|
+
"jq -r '.port_allocations[] | select(.label==\"Web Dev\") | .port' .codebyplan/server.json 2>/dev/null | head -1",
|
|
35
|
+
{ encoding: "utf-8" }
|
|
36
|
+
).trim();
|
|
37
|
+
const port = parseInt(raw, 10);
|
|
38
|
+
return `http://localhost:${port}`;
|
|
39
|
+
} catch {
|
|
40
|
+
return "http://localhost:3010"; // fallback
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
testDir: "apps/web/e2e",
|
|
46
|
+
fullyParallel: false,
|
|
47
|
+
forbidOnly: !!process.env.CI,
|
|
48
|
+
retries: process.env.CI ? 2 : 0,
|
|
49
|
+
workers: 1, // serialize against shared remote Supabase — see e2e.md § Supabase Parallelism
|
|
50
|
+
reporter: process.env.CI ? "github" : "html",
|
|
51
|
+
globalSetup: "./apps/web/e2e/global-setup", // string path — resolved relative to config; safe under ESM
|
|
52
|
+
use: {
|
|
53
|
+
baseURL: getBaseUrl(),
|
|
54
|
+
trace: "on-first-retry",
|
|
55
|
+
screenshot: "only-on-failure",
|
|
56
|
+
},
|
|
57
|
+
projects: [
|
|
58
|
+
{ name: "setup", testMatch: /global\.setup\.ts/ },
|
|
59
|
+
{
|
|
60
|
+
name: "web",
|
|
61
|
+
use: { ...devices["Desktop Chrome"], storageState: "apps/web/e2e/.auth/user.json" },
|
|
62
|
+
dependencies: ["setup"],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
webServer: {
|
|
66
|
+
command: "pnpm --filter @codebyplan/web dev",
|
|
67
|
+
url: getBaseUrl(),
|
|
68
|
+
reuseExistingServer: !process.env.CI,
|
|
69
|
+
timeout: 120_000,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Key options:
|
|
75
|
+
|
|
76
|
+
| Option | Why |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `workers: 1` | Prevents auth/RLS races on a shared remote Supabase project |
|
|
79
|
+
| `globalSetup` | Logs in once, writes `storageState` so tests start authenticated |
|
|
80
|
+
| `reuseExistingServer` | Skip dev-server startup when already running locally |
|
|
81
|
+
|
|
82
|
+
## Auth — global setup + storage state
|
|
83
|
+
|
|
84
|
+
Create `apps/web/e2e/global-setup.ts`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { chromium, FullConfig } from "@playwright/test";
|
|
88
|
+
import path from "path";
|
|
89
|
+
|
|
90
|
+
const AUTH_FILE = path.join(__dirname, ".auth/user.json");
|
|
91
|
+
|
|
92
|
+
export default async function globalSetup(config: FullConfig) {
|
|
93
|
+
const email = process.env.E2E_TEST_EMAIL;
|
|
94
|
+
const password = process.env.E2E_TEST_PASSWORD;
|
|
95
|
+
|
|
96
|
+
if (!email || !password) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"E2E_TEST_EMAIL and E2E_TEST_PASSWORD must be set.\n" +
|
|
99
|
+
"Copy .env.local.example to .env.local, then run: pnpm e2e:provision"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { baseURL } = config.projects[0].use;
|
|
104
|
+
const browser = await chromium.launch();
|
|
105
|
+
const page = await browser.newPage();
|
|
106
|
+
|
|
107
|
+
await page.goto(`${baseURL}/login`);
|
|
108
|
+
await page.getByLabel(/email/i).fill(email);
|
|
109
|
+
await page.getByLabel(/password/i).fill(password);
|
|
110
|
+
await page.getByRole("button", { name: /sign in|log in/i }).click();
|
|
111
|
+
await page.waitForURL(/\/(dashboard|home|app)/, { timeout: 15_000 });
|
|
112
|
+
|
|
113
|
+
// Warm up the first route to avoid cold-start timeouts in specs
|
|
114
|
+
await page.goto(baseURL!);
|
|
115
|
+
await page.context().storageState({ path: AUTH_FILE });
|
|
116
|
+
await browser.close();
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Gitignore the auth state — run before first use:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
mkdir -p apps/web/e2e/.auth
|
|
124
|
+
echo "apps/web/e2e/.auth/" >> .gitignore
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Auth probe spec
|
|
128
|
+
|
|
129
|
+
`apps/web/e2e/_probe/auth.spec.ts` — validates the login path directly (outside
|
|
130
|
+
storage-state flow) so credential failures are diagnosed cleanly:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { test, expect } from "@playwright/test";
|
|
134
|
+
|
|
135
|
+
test("auth probe: can log in with E2E_TEST_EMAIL/E2E_TEST_PASSWORD", async ({
|
|
136
|
+
page,
|
|
137
|
+
}) => {
|
|
138
|
+
const email = process.env.E2E_TEST_EMAIL;
|
|
139
|
+
const password = process.env.E2E_TEST_PASSWORD;
|
|
140
|
+
expect(email, "E2E_TEST_EMAIL env var is required").toBeTruthy();
|
|
141
|
+
expect(password, "E2E_TEST_PASSWORD env var is required").toBeTruthy();
|
|
142
|
+
|
|
143
|
+
await page.goto("/login");
|
|
144
|
+
await page.getByLabel(/email/i).fill(email!);
|
|
145
|
+
await page.getByLabel(/password/i).fill(password!);
|
|
146
|
+
await page.getByRole("button", { name: /sign in|log in/i }).click();
|
|
147
|
+
|
|
148
|
+
await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 15_000 });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Run the probe before the full suite: `pnpm exec playwright test --project=web _probe/auth`.
|
|
153
|
+
|
|
154
|
+
## Provision script convention
|
|
155
|
+
|
|
156
|
+
Every repo with Playwright auth ships `scripts/provision-e2e-user.ts` — an idempotent
|
|
157
|
+
script that creates the test user in the dev Supabase project. Wire it to `package.json`:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"scripts": {
|
|
162
|
+
"e2e:provision": "tsx scripts/provision-e2e-user.ts"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The skill records the path; the repo author writes the script. See
|
|
168
|
+
`.claude/context/testing/e2e.md` § Provisioning Playwright credentials for the full
|
|
169
|
+
contract (idempotency, multi-tenant subdomain, `.env.local.example`).
|
|
170
|
+
|
|
171
|
+
## CI secrets
|
|
172
|
+
|
|
173
|
+
Add these four secrets to the GitHub repo (Settings → Secrets → Actions):
|
|
174
|
+
|
|
175
|
+
| Secret | Purpose |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `E2E_TEST_EMAIL` | Test account email |
|
|
178
|
+
| `E2E_TEST_PASSWORD` | Test account password |
|
|
179
|
+
| `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL |
|
|
180
|
+
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | Supabase publishable (anon) key |
|
|
181
|
+
|
|
182
|
+
## GitHub Actions snippet
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
- name: Install Playwright browsers
|
|
186
|
+
run: pnpm exec playwright install --with-deps chromium
|
|
187
|
+
|
|
188
|
+
- name: Run Playwright tests
|
|
189
|
+
run: pnpm exec playwright test
|
|
190
|
+
env:
|
|
191
|
+
E2E_TEST_EMAIL: ${{ secrets.E2E_TEST_EMAIL }}
|
|
192
|
+
E2E_TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
|
193
|
+
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
|
|
194
|
+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY }}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Pitfalls
|
|
198
|
+
|
|
199
|
+
**Cold-start timeouts** — Next.js dev mode compiles routes lazily. The warmup fetch in
|
|
200
|
+
`globalSetup` (after `page.goto(baseURL!)`) primes the compiler before specs run.
|
|
201
|
+
|
|
202
|
+
**SCSS Module selectors** — prefer `[class*='componentName']` with `.first()` over
|
|
203
|
+
positional `.nth(N)` locators. Prefer `getByRole`/`getByLabel`/`getByTestId` when
|
|
204
|
+
accessible names are available.
|
|
205
|
+
|
|
206
|
+
**SCSS import errors in tests** — Playwright runs in Node, not webpack. If your test
|
|
207
|
+
imports a component that imports SCSS, configure `playwright.config.ts` to use
|
|
208
|
+
`@playwright/test`'s built-in transform or exclude such imports.
|
|
209
|
+
|
|
210
|
+
**Port mismatch** — before running, compare `playwright.config.ts` `baseURL` port with
|
|
211
|
+
`.codebyplan/server.json`. On mismatch, the `cbp-e2e-playwright` agent will ask which is
|
|
212
|
+
correct — do not guess.
|