@delt/tester-mcp 0.1.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.
Files changed (51) hide show
  1. package/README.md +36 -0
  2. package/bin/tester-mcp.js +2 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +106 -0
  5. package/dist/config/loadConfig.d.ts +14 -0
  6. package/dist/config/loadConfig.js +20 -0
  7. package/dist/env/captureEnv.d.ts +8 -0
  8. package/dist/env/captureEnv.js +20 -0
  9. package/dist/guide/loadGuide.d.ts +6 -0
  10. package/dist/guide/loadGuide.js +23 -0
  11. package/dist/init.d.ts +17 -0
  12. package/dist/init.js +127 -0
  13. package/dist/result/parseExecutorResult.d.ts +11 -0
  14. package/dist/result/parseExecutorResult.js +29 -0
  15. package/dist/result/types.d.ts +44 -0
  16. package/dist/result/types.js +1 -0
  17. package/dist/result/writeResult.d.ts +3 -0
  18. package/dist/result/writeResult.js +22 -0
  19. package/dist/run/buildExecutorArgs.d.ts +6 -0
  20. package/dist/run/buildExecutorArgs.js +39 -0
  21. package/dist/run/buildPrompt.d.ts +7 -0
  22. package/dist/run/buildPrompt.js +84 -0
  23. package/dist/run/runScenario.d.ts +18 -0
  24. package/dist/run/runScenario.js +26 -0
  25. package/dist/run/runScenarios.d.ts +11 -0
  26. package/dist/run/runScenarios.js +47 -0
  27. package/dist/run/spawnExecutor.d.ts +34 -0
  28. package/dist/run/spawnExecutor.js +93 -0
  29. package/dist/run/streamParser.d.ts +17 -0
  30. package/dist/run/streamParser.js +51 -0
  31. package/dist/run/summarizeLine.d.ts +1 -0
  32. package/dist/run/summarizeLine.js +29 -0
  33. package/dist/scenario/actions.d.ts +5 -0
  34. package/dist/scenario/actions.js +33 -0
  35. package/dist/scenario/expandScenarioPaths.d.ts +1 -0
  36. package/dist/scenario/expandScenarioPaths.js +31 -0
  37. package/dist/scenario/parseScenario.d.ts +2 -0
  38. package/dist/scenario/parseScenario.js +37 -0
  39. package/dist/scenario/types.d.ts +47 -0
  40. package/dist/scenario/types.js +1 -0
  41. package/dist/secrets/loadSecretsFile.d.ts +1 -0
  42. package/dist/secrets/loadSecretsFile.js +10 -0
  43. package/dist/secrets/redactSecrets.d.ts +6 -0
  44. package/dist/secrets/redactSecrets.js +46 -0
  45. package/dist/secrets/resolveSecrets.d.ts +5 -0
  46. package/dist/secrets/resolveSecrets.js +16 -0
  47. package/dist/util/runId.d.ts +1 -0
  48. package/dist/util/runId.js +3 -0
  49. package/package.json +19 -0
  50. package/skills/tester-mcp/SKILL.md +29 -0
  51. package/skills/tester-mcp/document-guide.md +146 -0
@@ -0,0 +1,5 @@
1
+ export interface ResolveOpts {
2
+ secrets?: Record<string, unknown>;
3
+ env?: Record<string, string | undefined>;
4
+ }
5
+ export declare function resolveSecrets(value: string, opts?: ResolveOpts): string;
@@ -0,0 +1,16 @@
1
+ // ${secrets.a.b} → secrets file (nested) first, else env SECRET_A_B; throw if missing.
2
+ export function resolveSecrets(value, opts = {}) {
3
+ const env = opts.env ?? process.env;
4
+ return value.replace(/\$\{secrets\.([\w.]+)\}/g, (_m, path) => {
5
+ const fromFile = path
6
+ .split(".")
7
+ .reduce((o, k) => (o == null ? undefined : o[k]), opts.secrets);
8
+ if (typeof fromFile === "string")
9
+ return fromFile;
10
+ const key = "SECRET_" + path.replace(/\./g, "_").toUpperCase();
11
+ const v = env[key];
12
+ if (v === undefined)
13
+ throw new Error(`시크릿 누락: ${path} (tester-mcp.secrets.yaml 의 ${path} 또는 env ${key})`);
14
+ return String(v);
15
+ });
16
+ }
@@ -0,0 +1 @@
1
+ export declare function makeRunId(now?: Date): string;
@@ -0,0 +1,3 @@
1
+ export function makeRunId(now = new Date()) {
2
+ return now.toISOString().replace(/\..+$/, "").replace(/:/g, "-");
3
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@delt/tester-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Opus(planner) + Haiku(executor) + Chrome 통합 테스트 오케스트레이터",
5
+ "type": "module",
6
+ "bin": { "tester-mcp": "bin/tester-mcp.js" },
7
+ "files": ["dist", "bin", "skills"],
8
+ "publishConfig": { "access": "public" },
9
+ "scripts": {
10
+ "build": "tsc -p tsconfig.json",
11
+ "dev": "tsx src/cli.ts",
12
+ "test": "vitest run"
13
+ },
14
+ "engines": { "node": ">=20" },
15
+ "dependencies": { "commander": "^12.1.0", "yaml": "^2.5.0" },
16
+ "devDependencies": {
17
+ "typescript": "^5.5.0", "tsx": "^4.16.0", "vitest": "^2.0.0", "@types/node": "^20.14.0"
18
+ }
19
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Run browser screen E2E tests. Use when the user asks to test a web screen or flow, verify a login or form, or check a UI after a change. Writes a scenario YAML, runs the tester-mcp CLI to drive a claude-in-chrome executor, and reports PASS / PARTIAL / FAIL / NOT_TESTED.
3
+ allowed-tools: Bash(tester-mcp *) Read Write Edit Glob Grep
4
+ ---
5
+
6
+ # tester-mcp — Screen E2E testing
7
+
8
+ Current CLI usage (always up to date):
9
+
10
+ !`tester-mcp --help`
11
+
12
+ > Prerequisites and the full scenario DSL (single source of truth): run `tester-mcp document-guide`.
13
+
14
+ ## Workflow (document first)
15
+
16
+ 1. **Write the scenario** — `scenarios/<area>/<id>.yaml`. **Resolve a stable `css` or `role` selector from the Vue/PrimeVue source and put it in the `target`** — don't rely on the executor to find elements by natural language (it tries once, then bails NOT_TESTED). `description`/`text` are last-resort fallbacks. Pin the language with `locale:`, reference secrets as `${secrets...}`. Full schema: `tester-mcp document-guide`.
17
+ 2. **Run** — `tester-mcp run <scenarios...> -c <config>`. The CLI spawns the executor(s) and waits. Pass multiple scenarios (files or a directory) to run them in parallel; `--concurrency <1-10>` caps how many run at once (default `min(count, 10)`). Each executor creates its OWN browser tab (via tabs_create), so they run truly in parallel (~2-3× at 4-way). They still share one Chrome, so heavy scenarios contend somewhat — keep concurrency modest for heavy flows.
18
+ 3. **Branch on the result label**:
19
+ - PASS / PARTIAL → report the evidence and screenshots.
20
+ - FAIL → present the contradicting evidence, screenshots, and `handoff_notes`, then move into a fix.
21
+ - NOT_TESTED → give the reason plus `pattern_inference` (assumed_ok/unknown); state the missing precondition, or hand off to a human after repeated failure. Read the result's executor_log (full tool trail) to diagnose, then fix the scenario's selectors.
22
+
23
+ ## Secrets
24
+
25
+ The test account lives in the gitignored `tester-mcp.secrets.yaml` (or env `SECRET_*` in CI); reference it as `${secrets.tester.username}`. If it is missing, tell the user to fill it in. Details: `tester-mcp document-guide`.
26
+
27
+ ## Discipline
28
+
29
+ Grep for references to any changed symbol and check runtime dependencies (storage, store, API params). Separate proof from inference — report "verified X, inferred Y by the same pattern", never absolute claims.
@@ -0,0 +1,146 @@
1
+ # tester-mcp — Authoring Guide
2
+
3
+ This guide is printed by `tester-mcp document-guide`. It is the single source of
4
+ truth for prerequisites and the scenario DSL. Read it before writing scenarios.
5
+
6
+ ## Prerequisites
7
+
8
+ - Node.js >= 20.
9
+ - The `claude` CLI must be installed and **logged in** (the executor runs
10
+ `claude -p --chrome`).
11
+ - The claude-in-chrome browser extension must be installed and connected. The
12
+ executor drives a **real, visible Chrome window** — it is NOT headless and it
13
+ keeps cookies/session. Chrome/Edge only.
14
+ - On **Windows**, the `--chrome` flag is required for claude-in-chrome to work in
15
+ PowerShell. **WSL is not supported.**
16
+ - Run `tester-mcp init` once per project to install the skill and scaffold
17
+ `tester-mcp.config.yaml` plus a secrets example.
18
+
19
+ ## Running
20
+
21
+ ```
22
+ tester-mcp run scenarios/<area>/<id>.yaml -c tester-mcp.config.yaml
23
+ ```
24
+
25
+ The CLI spawns the executor, waits, and writes a result to `runs/<runId>/`.
26
+ A hard timeout (default 5 min, `runner.timeout_ms` or `--timeout`) kills a stuck
27
+ executor and reports NOT_TESTED.
28
+
29
+ ### Running multiple scenarios in parallel
30
+
31
+ Pass several scenario files (or a directory) to run them concurrently — each
32
+ scenario gets its own executor process and creates its OWN browser tab (via
33
+ tabs_create) inside the shared Chrome tab group, so they run in parallel
34
+ (~2-3× at 4-way; one Chrome still serializes part of the work, so expect some
35
+ contention and varied finish times, not a clean N×):
36
+
37
+ ```
38
+ tester-mcp run scenarios/a.yaml scenarios/b.yaml --concurrency 3 -c tester-mcp.config.yaml
39
+ tester-mcp run scenarios/auth/ --concurrency 5 -c tester-mcp.config.yaml
40
+ ```
41
+
42
+ - `--concurrency <1-10>`: how many run at once (default `min(count, 10)`, hard cap 10).
43
+ - Each scenario writes its own `runs/<runId>/<id>.json` + `.log`.
44
+ - Logins are per-tab isolated (auth in sessionStorage), so concurrent logins are safe.
45
+ - But `localStorage` (e.g. `languageType`) is shared across tabs — keep parallel scenarios on the **same locale**, or run different locales serially.
46
+ - Slow scenarios under contention may exceed the timeout — raise `--timeout` (e.g. 240000–300000) for parallel batches.
47
+ - Each scenario `id` must be unique across the batch (results/logs are keyed by `id`).
48
+
49
+ ## Scenario file
50
+
51
+ A scenario is one YAML file. Fields:
52
+
53
+ - `id` (string, required) — stable identifier, used in the result filename.
54
+ - `title` (string, required) — human-readable summary.
55
+ - `steps` (list, required) — ordered actions (see below).
56
+ - `locale` (string, optional) — pins UI language: `kg` (Kyrgyz), `ru` (Russian),
57
+ `kr` (Korean). The executor switches the app to this language first.
58
+ - `login_as` (string, optional) — named login to perform before the steps.
59
+ - `on_failure` (string, optional) — `stop` (default) or `continue`.
60
+ - `optional` (bool, optional) — if true, a FAIL is downgraded to a soft signal.
61
+ - `defaults` (map, optional) — default values reused across steps.
62
+ - `precondition` (string, optional) — human note on required data/state.
63
+
64
+ ## Actions (each item in `steps`)
65
+
66
+ - `navigate` — `{ action: navigate, url: "/path" }` (relative to `targets.frontend`).
67
+ - `fill` — `{ action: fill, target: <target>, value: "..." }`.
68
+ - `click` — `{ action: click, target: <target> }`.
69
+ - `wait_for` — `{ action: wait_for, target: <target> }`.
70
+ - `assert_visible` — `{ action: assert_visible, target: <target> }`.
71
+ - `screenshot` — `{ action: screenshot, name: "after-login" }`.
72
+
73
+ ## Target (how to locate an element)
74
+
75
+ `target` is a multi-strategy object. Provide one or more; the executor tries them
76
+ in order of specificity. There is no testid in this project, so prefer stable
77
+ attributes and visible text.
78
+
79
+ - `css` — CSS selector.
80
+ - `placeholder` — input placeholder text.
81
+ - `label` — associated label text.
82
+ - `text` — visible text content.
83
+ - `role` — ARIA role (e.g. `button`).
84
+ - `description` — natural-language fallback ("the blue Login button").
85
+
86
+ Example:
87
+ ```yaml
88
+ target:
89
+ placeholder: "Username"
90
+ description: "the username input on the login form"
91
+ ```
92
+
93
+ **Authoring rule (selector-first).** Resolve a stable `css` or `role`+name from the
94
+ component source (Vue/PrimeVue) and put it in the target. `description`/`text` are
95
+ last-resort fallbacks, not the primary strategy. The executor tries the target's
96
+ strategies **once** and does NOT grope the page — on a first-attempt miss it bails
97
+ with NOT_TESTED and reports what it actually saw. A precise selector is what drives
98
+ pass rate and speed. For multi-step UI (filters, dropdowns, modals), script the
99
+ open→select sequence as explicit steps with `wait_for` between them.
100
+
101
+ ## Secrets
102
+
103
+ Never inline credentials. Reference them as `${secrets.a.b}`:
104
+ ```yaml
105
+ - { action: fill, target: { placeholder: "Username" }, value: "${secrets.tester.username}" }
106
+ - { action: fill, target: { placeholder: "Password" }, value: "${secrets.tester.password}" }
107
+ ```
108
+ Resolution order: the file `tester-mcp.secrets.yaml` (gitignored) first, then the
109
+ environment variable `SECRET_A_B` (uppercased, dot → underscore). Secret values
110
+ are redacted to `***` in stored results.
111
+
112
+ ## Ephemeral UI (toasts, snackbars)
113
+
114
+ Short-lived elements (e.g. PrimeVue toast, `life:3000`) vanish faster than tool
115
+ round-trips. To verify them reliably:
116
+ - Set `ephemeral: true` on the scenario.
117
+ - Assert immediately after the trigger with ONE fast text/DOM check — do NOT chain
118
+ fallbacks (JS → find → read_page); the element disappears mid-chain.
119
+ - Do NOT add a `screenshot` step for an ephemeral element — the assertion IS the
120
+ proof, and a screenshot of a vanished element causes retry loops.
121
+ - (Optional) In a test build, raise the toast `life` so it stays long enough.
122
+
123
+ ## Result labels
124
+
125
+ - `PASS` — every assertion verified at runtime.
126
+ - `PARTIAL` — some verified, some not (each item carries proof or reason).
127
+ - `FAIL` — an assertion was contradicted at runtime.
128
+ - `NOT_TESTED` — could not run (timeout, missing data, blocked prerequisite).
129
+
130
+ On NOT_TESTED, read the run's `executor_log` (path is in the result JSON) to see the
131
+ tool sequence and errors — that's how the Planner diagnoses and fixes the scenario.
132
+
133
+ ## Minimal example
134
+
135
+ ```yaml
136
+ id: login-success
137
+ title: Login with valid credentials reaches the dashboard
138
+ locale: ru
139
+ steps:
140
+ - { action: navigate, url: "/" }
141
+ - { action: fill, target: { placeholder: "Username" }, value: "${secrets.tester.username}" }
142
+ - { action: fill, target: { placeholder: "Password" }, value: "${secrets.tester.password}" }
143
+ - { action: click, target: { text: "Login", role: "button" } }
144
+ - { action: assert_visible, target: { description: "the main dashboard after login" } }
145
+ - { action: screenshot, name: "dashboard" }
146
+ ```