@eidra-umain/greenlight 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 (142) hide show
  1. package/README.md +391 -0
  2. package/dist/browser/browser.d.ts +24 -0
  3. package/dist/browser/browser.d.ts.map +1 -0
  4. package/dist/browser/browser.js +44 -0
  5. package/dist/browser/browser.js.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +140 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/run.d.ts +9 -0
  11. package/dist/cli/run.d.ts.map +1 -0
  12. package/dist/cli/run.js +277 -0
  13. package/dist/cli/run.js.map +1 -0
  14. package/dist/config.d.ts +48 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +107 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/globals.d.ts +21 -0
  19. package/dist/globals.d.ts.map +1 -0
  20. package/dist/globals.js +24 -0
  21. package/dist/globals.js.map +1 -0
  22. package/dist/parser/loader.d.ts +7 -0
  23. package/dist/parser/loader.d.ts.map +1 -0
  24. package/dist/parser/loader.js +43 -0
  25. package/dist/parser/loader.js.map +1 -0
  26. package/dist/parser/schema.d.ts +42 -0
  27. package/dist/parser/schema.d.ts.map +1 -0
  28. package/dist/parser/schema.js +33 -0
  29. package/dist/parser/schema.js.map +1 -0
  30. package/dist/parser/steps.d.ts +13 -0
  31. package/dist/parser/steps.d.ts.map +1 -0
  32. package/dist/parser/steps.js +44 -0
  33. package/dist/parser/steps.js.map +1 -0
  34. package/dist/parser/variables.d.ts +18 -0
  35. package/dist/parser/variables.d.ts.map +1 -0
  36. package/dist/parser/variables.js +44 -0
  37. package/dist/parser/variables.js.map +1 -0
  38. package/dist/pilot/a11y-parser.d.ts +26 -0
  39. package/dist/pilot/a11y-parser.d.ts.map +1 -0
  40. package/dist/pilot/a11y-parser.js +195 -0
  41. package/dist/pilot/a11y-parser.js.map +1 -0
  42. package/dist/pilot/assertions.d.ts +30 -0
  43. package/dist/pilot/assertions.d.ts.map +1 -0
  44. package/dist/pilot/assertions.js +219 -0
  45. package/dist/pilot/assertions.js.map +1 -0
  46. package/dist/pilot/checkbox.d.ts +12 -0
  47. package/dist/pilot/checkbox.d.ts.map +1 -0
  48. package/dist/pilot/checkbox.js +104 -0
  49. package/dist/pilot/checkbox.js.map +1 -0
  50. package/dist/pilot/executor.d.ts +17 -0
  51. package/dist/pilot/executor.d.ts.map +1 -0
  52. package/dist/pilot/executor.js +462 -0
  53. package/dist/pilot/executor.js.map +1 -0
  54. package/dist/pilot/form-fields.d.ts +34 -0
  55. package/dist/pilot/form-fields.d.ts.map +1 -0
  56. package/dist/pilot/form-fields.js +139 -0
  57. package/dist/pilot/form-fields.js.map +1 -0
  58. package/dist/pilot/llm.d.ts +49 -0
  59. package/dist/pilot/llm.d.ts.map +1 -0
  60. package/dist/pilot/llm.js +188 -0
  61. package/dist/pilot/llm.js.map +1 -0
  62. package/dist/pilot/locator.d.ts +58 -0
  63. package/dist/pilot/locator.d.ts.map +1 -0
  64. package/dist/pilot/locator.js +248 -0
  65. package/dist/pilot/locator.js.map +1 -0
  66. package/dist/pilot/message-builder.d.ts +31 -0
  67. package/dist/pilot/message-builder.d.ts.map +1 -0
  68. package/dist/pilot/message-builder.js +112 -0
  69. package/dist/pilot/message-builder.js.map +1 -0
  70. package/dist/pilot/network.d.ts +23 -0
  71. package/dist/pilot/network.d.ts.map +1 -0
  72. package/dist/pilot/network.js +92 -0
  73. package/dist/pilot/network.js.map +1 -0
  74. package/dist/pilot/pilot.d.ts +27 -0
  75. package/dist/pilot/pilot.d.ts.map +1 -0
  76. package/dist/pilot/pilot.js +249 -0
  77. package/dist/pilot/pilot.js.map +1 -0
  78. package/dist/pilot/prompts.d.ts +8 -0
  79. package/dist/pilot/prompts.d.ts.map +1 -0
  80. package/dist/pilot/prompts.js +163 -0
  81. package/dist/pilot/prompts.js.map +1 -0
  82. package/dist/pilot/providers/anthropic.d.ts +6 -0
  83. package/dist/pilot/providers/anthropic.d.ts.map +1 -0
  84. package/dist/pilot/providers/anthropic.js +45 -0
  85. package/dist/pilot/providers/anthropic.js.map +1 -0
  86. package/dist/pilot/providers/gemini.d.ts +6 -0
  87. package/dist/pilot/providers/gemini.d.ts.map +1 -0
  88. package/dist/pilot/providers/gemini.js +55 -0
  89. package/dist/pilot/providers/gemini.js.map +1 -0
  90. package/dist/pilot/providers/index.d.ts +10 -0
  91. package/dist/pilot/providers/index.d.ts.map +1 -0
  92. package/dist/pilot/providers/index.js +25 -0
  93. package/dist/pilot/providers/index.js.map +1 -0
  94. package/dist/pilot/providers/openai-compatible.d.ts +7 -0
  95. package/dist/pilot/providers/openai-compatible.d.ts.map +1 -0
  96. package/dist/pilot/providers/openai-compatible.js +34 -0
  97. package/dist/pilot/providers/openai-compatible.js.map +1 -0
  98. package/dist/pilot/providers/types.d.ts +12 -0
  99. package/dist/pilot/providers/types.d.ts.map +1 -0
  100. package/dist/pilot/providers/types.js +2 -0
  101. package/dist/pilot/providers/types.js.map +1 -0
  102. package/dist/pilot/response-parser.d.ts +31 -0
  103. package/dist/pilot/response-parser.d.ts.map +1 -0
  104. package/dist/pilot/response-parser.js +188 -0
  105. package/dist/pilot/response-parser.js.map +1 -0
  106. package/dist/pilot/state.d.ts +19 -0
  107. package/dist/pilot/state.d.ts.map +1 -0
  108. package/dist/pilot/state.js +67 -0
  109. package/dist/pilot/state.js.map +1 -0
  110. package/dist/pilot/trace.d.ts +16 -0
  111. package/dist/pilot/trace.d.ts.map +1 -0
  112. package/dist/pilot/trace.js +117 -0
  113. package/dist/pilot/trace.js.map +1 -0
  114. package/dist/planner/hasher.d.ts +14 -0
  115. package/dist/planner/hasher.d.ts.map +1 -0
  116. package/dist/planner/hasher.js +21 -0
  117. package/dist/planner/hasher.js.map +1 -0
  118. package/dist/planner/plan-generator.d.ts +23 -0
  119. package/dist/planner/plan-generator.d.ts.map +1 -0
  120. package/dist/planner/plan-generator.js +56 -0
  121. package/dist/planner/plan-generator.js.map +1 -0
  122. package/dist/planner/plan-runner.d.ts +16 -0
  123. package/dist/planner/plan-runner.d.ts.map +1 -0
  124. package/dist/planner/plan-runner.js +375 -0
  125. package/dist/planner/plan-runner.js.map +1 -0
  126. package/dist/planner/plan-store.d.ts +22 -0
  127. package/dist/planner/plan-store.d.ts.map +1 -0
  128. package/dist/planner/plan-store.js +71 -0
  129. package/dist/planner/plan-store.js.map +1 -0
  130. package/dist/planner/plan-types.d.ts +64 -0
  131. package/dist/planner/plan-types.d.ts.map +1 -0
  132. package/dist/planner/plan-types.js +7 -0
  133. package/dist/planner/plan-types.js.map +1 -0
  134. package/dist/reporter/types.d.ts +130 -0
  135. package/dist/reporter/types.d.ts.map +1 -0
  136. package/dist/reporter/types.js +5 -0
  137. package/dist/reporter/types.js.map +1 -0
  138. package/dist/types.d.ts +51 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +23 -0
  141. package/dist/types.js.map +1 -0
  142. package/package.json +64 -0
package/README.md ADDED
@@ -0,0 +1,391 @@
1
+ <p align="center">
2
+ <img src="assets/greenlight_banner.png" alt="GreenLight — AI-driven E2E Testing" width="500">
3
+ </p>
4
+
5
+ # GreenLight
6
+
7
+ Natural language driven end-to-end testing for web applications. Write tests as plain-English user stories, and an AI agent (the Pilot) executes them against your staging environment using a real browser.
8
+
9
+ No selectors. No XPaths. No test IDs, drivers or glue code. Just describe what a user would do.
10
+
11
+ ---
12
+
13
+ **[How it works](#how-it-works)** | **[Quick start](#quick-start)** | **[Project configuration](#project-configuration)** | **[CLI](#cli)** | **[Test syntax](#test-syntax)** | **[Cached plans](#cached-plans)** | **[LLM setup](#llm-setup)** | **[Architecture](#architecture)** | **[CI/CD](#cicd)**
14
+
15
+ ---
16
+
17
+ ## How it works
18
+
19
+ ```yaml
20
+ suite: "Product Search"
21
+ base_url: "https://staging.example.com"
22
+
23
+ tests:
24
+ - name: "Filtering reduces results"
25
+ steps:
26
+ - navigate to Products from the main menu
27
+ - remember the number of results shown
28
+ - select "Electronics" in the category filter
29
+ - check that the number of results has decreased
30
+ - search for "wireless headphones"
31
+ - check that the page contains "wireless"
32
+ - fill in the inquiry form with email "test@example.com" and some test data
33
+ - submit the form
34
+ - check that you see "Thanks for your inquiry"
35
+ ```
36
+
37
+ GreenLight understands form wizards, custom dropdowns, autocomplete fields, and checkbox consent flows. It fills in forms with realistic test data, handles before/after value comparisons, and works with any UI framework.
38
+
39
+ The first run uses an LLM to discover the right actions (the **discovery run**). After that, GreenLight caches a concrete action plan and replays it without LLM calls — making subsequent runs fast and deterministic.
40
+
41
+ ## Quick start
42
+
43
+ 1. Add GreenLight to your project:
44
+
45
+ ```bash
46
+ npm install @eidra-umain/greenlight
47
+ ```
48
+
49
+ 2. Create a `greenlight.yaml` in your project root:
50
+
51
+ ```yaml
52
+ suites:
53
+ - tests/e2e/login.yaml
54
+ - tests/e2e/checkout.yaml
55
+
56
+ deployments:
57
+ staging:
58
+ base_url: https://staging.myapp.com
59
+ ```
60
+
61
+ 3. Run:
62
+
63
+ ```bash
64
+ greenlight run
65
+ ```
66
+
67
+ ## Project configuration
68
+
69
+ GreenLight looks for a `greenlight.yaml` in the working directory. This file defines which suites to run and supports multiple deployment targets.
70
+
71
+ ### Single deployment
72
+
73
+ When there is only one deployment, it is used automatically:
74
+
75
+ ```yaml
76
+ suites:
77
+ - tests/e2e/*.yaml
78
+
79
+ deployments:
80
+ staging:
81
+ base_url: https://staging.myapp.com
82
+ ```
83
+
84
+ ### Multiple deployments
85
+
86
+ ```yaml
87
+ suites:
88
+ - tests/e2e/*.yaml
89
+
90
+ model: anthropic/claude-sonnet-4
91
+ timeout: 15000
92
+
93
+ deployments:
94
+ dev:
95
+ base_url: https://dev.myapp.com
96
+ staging:
97
+ base_url: https://staging.myapp.com
98
+ prod:
99
+ base_url: https://myapp.com
100
+ timeout: 30000
101
+
102
+ default_deployment: staging
103
+ ```
104
+
105
+ Shared settings go at the top level. Deployment-specific settings override them.
106
+
107
+ ```bash
108
+ greenlight run # uses default_deployment (staging)
109
+ greenlight run -d prod # selects the prod deployment
110
+ greenlight run -d dev # selects the dev deployment
111
+ ```
112
+
113
+ If there are multiple deployments and no `default_deployment` is set, the `--deployment` flag is required.
114
+
115
+ ### All config fields
116
+
117
+ | Field | Type | Description |
118
+ |-------|------|-------------|
119
+ | `suites` | string[] | Paths or globs to suite YAML files (required) |
120
+ | `deployments` | map | Named deployment targets |
121
+ | `default_deployment` | string | Which deployment to use by default |
122
+ | `base_url` | string | Base URL for the site under test |
123
+ | `model` | string | LLM model identifier |
124
+ | `llm_base_url` | string | Base URL for the OpenAI-compatible API |
125
+ | `timeout` | number | Per-step timeout in milliseconds |
126
+ | `headed` | boolean | Run browser in visible mode |
127
+ | `parallel` | number | Number of concurrent test cases |
128
+ | `reporter` | string | Output format: `cli`, `json`, or `html` |
129
+ | `viewport` | object | `{ width, height }` for the browser viewport |
130
+
131
+ All fields except `suites` can appear at the top level or inside a deployment. Priority: **CLI flags > deployment > top-level config > built-in defaults**.
132
+
133
+ ## CLI
134
+
135
+ ```bash
136
+ greenlight run [suites...] # run suite YAML files (overrides greenlight.yaml)
137
+ greenlight run # run suites from greenlight.yaml
138
+ greenlight run -d, --deployment <name> # select a named deployment
139
+ greenlight run -t, --test <name> # filter by test name
140
+ greenlight run --base-url <url> # override base URL
141
+ greenlight run --headed # visible browser
142
+ greenlight run -p, --parallel 4 # concurrent test cases
143
+ greenlight run -r, --reporter json # json output (also: cli, html)
144
+ greenlight run -o, --output results.json # write to file
145
+ greenlight run --timeout 15000 # per-step timeout (ms)
146
+ greenlight run --model openai/gpt-4o # override LLM model
147
+ greenlight run --llm-base-url <url> # use a different OpenAI-compatible API
148
+ greenlight run --debug # verbose output (actions, LLM modes, timings)
149
+ greenlight run --trace # timestamped browser events for perf analysis
150
+ greenlight run --discover # force discovery run, ignore cached plans
151
+ greenlight run --plan-status # show cache status for all tests
152
+ greenlight run --on-drift rerun # re-discover on cached plan drift (default: fail)
153
+ ```
154
+
155
+ ## GreenLight philosophy compared to Gherkin/Cucumber
156
+
157
+ Traditional BDD tools like Cucumber use **Gherkin** — a structured `Given/When/Then` syntax where every step requires a developer-written **step definition** (glue code) that maps the English phrase to actual browser automation with CSS selectors or XPaths.
158
+
159
+ GreenLight takes a different approach:
160
+
161
+ | | GreenLight | Gherkin (Cucumber) |
162
+ |---|---|---|
163
+ | **Test language** | Freeform plain English | Structured `Given/When/Then` keywords |
164
+ | **Element targeting** | AI resolves via accessibility tree — no selectors | Developers write glue code with selectors/XPaths |
165
+ | **Maintenance** | Tests survive UI refactors that don't change behavior | Selector changes break tests, requiring glue code updates |
166
+ | **Authoring** | Non-technical testers, no code required | Readable specs, but developers must write step definitions |
167
+ | **Determinism** | Cached plans are deterministic; discovery runs have LLM variability | Fully deterministic — same input, same execution path |
168
+ | **Maturity** | New, LLM-dependent | Battle-tested (15+ years), broad ecosystem |
169
+
170
+ **In short:** Gherkin requires developers to bridge English and browser automation via step definitions. GreenLight uses AI as that bridge — eliminating the glue code layer at the cost of introducing LLM-dependent variability.
171
+
172
+ ## Test syntax
173
+
174
+ Tests are plain English. The Pilot interprets intent, so phrasing is flexible. Common patterns:
175
+
176
+ | Action | Example |
177
+ |--------|---------|
178
+ | Navigate | `go to "/products"` or `navigate to About from the menu` |
179
+ | Click | `click "Add to Cart"` or `click the Submit button` |
180
+ | Type | `enter "jane@example.com" into "Email"` |
181
+ | Select | `select "Canada" from "Country"` (works with native and custom dropdowns) |
182
+ | Form fill | `fill in the contact form with email "a@b.com" and some test data` |
183
+ | Autocomplete | `type "Stock" into the city field and select the first suggestion` |
184
+ | Check | `check the "I agree to terms" checkbox` |
185
+ | Remember | `remember the number of search results` |
186
+ | Compare | `check that the number of results is less than before` |
187
+ | Assert | `check that page contains "Order Confirmed"` |
188
+ | Multi-step | `Select Red - Green - Blue in the color picker` (auto-split into 3 clicks) |
189
+
190
+ ### Form filling
191
+
192
+ Steps like "fill in the form with some test data and submit it" are automatically expanded at runtime. GreenLight inspects the actual form fields (labels, placeholders, input types) and generates appropriate test data. Autocomplete fields are detected and handled with type-wait-select flows. Consent checkboxes are automatically checked.
193
+
194
+ ### Value comparisons
195
+
196
+ Remember a value before an action, then compare after:
197
+
198
+ ```yaml
199
+ steps:
200
+ - remember the total shown in the results badge
201
+ - apply the "In Stock" filter
202
+ - check that the total has decreased
203
+ ```
204
+
205
+ ### Reusable steps
206
+
207
+ Define common sequences at the suite level and invoke by name:
208
+
209
+ ```yaml
210
+ reusable_steps:
211
+ log in as admin:
212
+ - enter "{{admin_email}}" into "Email"
213
+ - enter "{{admin_password}}" into "Password"
214
+ - click "Sign In"
215
+
216
+ tests:
217
+ - name: "Admin can access settings"
218
+ steps:
219
+ - log in as admin
220
+ - click "Settings"
221
+ - check that page contains "Account Settings"
222
+ ```
223
+
224
+ ## Cached plans
225
+
226
+ The first run of a test uses LLM calls to discover the right browser actions (**discovery run**). After a successful run, GreenLight caches the concrete action sequence as a **heuristic plan** in `.greenlight/plans/`.
227
+
228
+ Subsequent runs replay the cached plan directly via Playwright — no LLM calls, no API costs, significantly faster. If you change the test steps in YAML, the hash changes and GreenLight automatically re-discovers.
229
+
230
+ ```bash
231
+ greenlight run # uses cached plans where available
232
+ greenlight run --discover # force fresh discovery, ignore cache
233
+ greenlight run --plan-status # show which tests have cached plans
234
+ ```
235
+
236
+ ## LLM setup
237
+
238
+ ### API key
239
+
240
+ Set your API key via the `LLM_API_KEY` environment variable or a `.env` file in the project root:
241
+
242
+ ```bash
243
+ LLM_API_KEY=sk-...
244
+ ```
245
+
246
+ The same key is used regardless of provider. `OPENROUTER_API_KEY` is also accepted for backward compatibility.
247
+
248
+ ### Providers
249
+
250
+ GreenLight supports four LLM providers with native API integrations:
251
+
252
+ | Provider | Value | API |
253
+ |----------|-------|-----|
254
+ | [OpenRouter](https://openrouter.ai) | `openrouter` (default) | Access all models through a single API |
255
+ | [OpenAI](https://platform.openai.com) | `openai` | GPT-4o, GPT-4o-mini, etc. |
256
+ | [Google Gemini](https://ai.google.dev) | `gemini` | Gemini 2.5 Flash, Pro, etc. |
257
+ | [Anthropic Claude](https://console.anthropic.com) | `claude` | Claude Sonnet, Haiku, etc. |
258
+
259
+ Set the provider in `greenlight.yaml` or via CLI:
260
+
261
+ ```yaml
262
+ provider: openai
263
+ ```
264
+
265
+ ```bash
266
+ greenlight run --provider gemini
267
+ ```
268
+
269
+ Only one provider is active at a time. OpenRouter is the default — it lets you access models from all vendors through a single API key, which is the easiest way to get started.
270
+
271
+ ### Model selection
272
+
273
+ GreenLight uses the LLM in two distinct roles with different requirements:
274
+
275
+ - **Planner** — interprets the test steps, splits compound actions, and expands form-filling steps. This runs once per test case and benefits from a more capable model for consistent, correct results.
276
+ - **Pilot** — resolves individual steps against the live page (picking which element to click/type). This runs many times per test and should use a fast, inexpensive model to keep costs and execution time low.
277
+
278
+ Configure both in `greenlight.yaml`:
279
+
280
+ ```yaml
281
+ provider: openrouter
282
+ model:
283
+ planner: anthropic/claude-sonnet-4 # smarter model, runs once per test
284
+ pilot: openai/gpt-4o-mini # fast model, runs per step
285
+ ```
286
+
287
+ Or use a single model for both roles:
288
+
289
+ ```yaml
290
+ model: anthropic/claude-sonnet-4
291
+ ```
292
+
293
+ The `--model` CLI flag sets both roles to the same model (useful for quick overrides):
294
+
295
+ ```bash
296
+ greenlight run --model openai/gpt-4o
297
+ ```
298
+
299
+ Model names must match the provider's naming convention:
300
+
301
+ | Provider | Example model names |
302
+ |----------|-------------------|
303
+ | OpenRouter | `anthropic/claude-sonnet-4`, `openai/gpt-4o-mini`, `google/gemini-2.5-flash` |
304
+ | OpenAI | `gpt-4o`, `gpt-4o-mini` |
305
+ | Gemini | `gemini-2.5-flash`, `gemini-2.5-pro` |
306
+ | Claude | `claude-sonnet-4-20250514`, `claude-haiku-4-20250514` |
307
+
308
+ ### Custom LLM endpoint
309
+
310
+ Each provider has a default API endpoint. You can override it with `llm_base_url` for proxies, self-hosted models, or compatible APIs:
311
+
312
+ ```yaml
313
+ # Local Ollama (OpenAI-compatible)
314
+ provider: openai
315
+ llm_base_url: http://localhost:11434/v1
316
+ model: llama3
317
+ ```
318
+
319
+ ```bash
320
+ greenlight run --provider openai --llm-base-url http://localhost:11434/v1 --model llama3
321
+ ```
322
+
323
+ ## Tech stack
324
+
325
+ | Layer | Technology |
326
+ |-------|-----------|
327
+ | Browser automation | Playwright (Chromium) |
328
+ | Page representation | Accessibility tree with stable element refs |
329
+ | AI | OpenRouter (any OpenAI-compatible provider) |
330
+ | Plan caching | SHA-256 hash-based invalidation, `.greenlight/plans/` |
331
+ | Test definitions | YAML |
332
+ | Language | TypeScript (Node.js, ESM) |
333
+
334
+ ## Architecture
335
+
336
+ ```mermaid
337
+ flowchart TD
338
+ subgraph cli[greenlight CLI]
339
+ yaml[YAML Parser]
340
+ output[CLI Output<br/>step results, pass/fail]
341
+
342
+ subgraph runner[Test Orchestrator]
343
+ orchestrator[Run Loop<br/>suite loading, browser lifecycle,<br/>plan cache decisions]
344
+ planner[Plan Cache<br/>.greenlight/plans/]
345
+
346
+ subgraph pilot[The Pilot — discovery run]
347
+ state[Page State Capture<br/>a11y snapshot + stable refs]
348
+ llm[LLM Client<br/>plan, resolve, expand]
349
+ executor[Action Executor<br/>click, type, select, autocomplete,<br/>check, remember, compare, ...]
350
+ end
351
+
352
+ replay[Plan Runner<br/>cached replay, no LLM]
353
+ end
354
+ end
355
+
356
+ subgraph browser[Browser Layer]
357
+ chromium[(Chromium<br/>Browser Context<br/>staging site)]
358
+ end
359
+
360
+ yaml --> orchestrator
361
+ orchestrator -->|no cache| pilot
362
+ orchestrator -->|cached| replay
363
+ orchestrator -->|save plan| planner
364
+ planner -->|load plan| orchestrator
365
+ state --> llm
366
+ llm --> executor
367
+ state <--> chromium
368
+ executor --> chromium
369
+ replay <--> chromium
370
+ orchestrator --> output
371
+ ```
372
+
373
+ **Discovery run:** capture page state (a11y tree with stable refs) → LLM determines action → execute via Playwright → record for cache.
374
+
375
+ **Cached run:** replay stored actions directly via Playwright — no LLM calls, no API costs.
376
+
377
+ ## Documentation
378
+
379
+ - [Specifications](docs/specifications.md) — full feature spec, technology decisions, MCP strategy
380
+ - [Implementation Plan](docs/implementation.md) — step-by-step build plan
381
+
382
+ ## CI/CD
383
+
384
+ ```yaml
385
+ - name: Run E2E tests
386
+ run: greenlight run -d staging --reporter json --output results.json
387
+ env:
388
+ OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
389
+ ```
390
+
391
+ Exit code 0 on all-pass, non-zero on any failure.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Playwright browser lifecycle management.
3
+ * Wraps launch, context creation, page creation, and cleanup.
4
+ */
5
+ import { type Browser, type BrowserContext, type Page } from "playwright";
6
+ import type { RunConfig } from "../types.js";
7
+ export interface BrowserOptions {
8
+ headed: boolean;
9
+ viewport: {
10
+ width: number;
11
+ height: number;
12
+ };
13
+ }
14
+ /** Launch a Chromium browser instance. */
15
+ export declare function launchBrowser(config: BrowserOptions): Promise<Browser>;
16
+ /** Create an isolated browser context with configured viewport and test mode headers. */
17
+ export declare function createContext(browser: Browser, config: BrowserOptions): Promise<BrowserContext>;
18
+ /** Create a new page within a browser context and inject test mode global. */
19
+ export declare function createPage(context: BrowserContext): Promise<Page>;
20
+ /** Close the browser and all its contexts. */
21
+ export declare function closeBrowser(browser: Browser): Promise<void>;
22
+ /** Extract browser options from RunConfig. */
23
+ export declare function toBrowserOptions(config: RunConfig): BrowserOptions;
24
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3C;AAED,0CAA0C;AAC1C,wBAAsB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAI5E;AAED,yFAAyF;AACzF,wBAAsB,aAAa,CAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,GACpB,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED,8EAA8E;AAC9E,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAUvE;AAED,8CAA8C;AAC9C,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,cAAc,CAKlE"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Playwright browser lifecycle management.
3
+ * Wraps launch, context creation, page creation, and cleanup.
4
+ */
5
+ import { chromium } from "playwright";
6
+ /** Launch a Chromium browser instance. */
7
+ export async function launchBrowser(config) {
8
+ return chromium.launch({
9
+ headless: !config.headed,
10
+ });
11
+ }
12
+ /** Create an isolated browser context with configured viewport and test mode headers. */
13
+ export async function createContext(browser, config) {
14
+ return browser.newContext({
15
+ viewport: config.viewport,
16
+ extraHTTPHeaders: {
17
+ "X-E2E-Test": "true",
18
+ },
19
+ });
20
+ }
21
+ /** Create a new page within a browser context and inject test mode global. */
22
+ export async function createPage(context) {
23
+ const page = await context.newPage();
24
+ await page.addInitScript(() => {
25
+ Object.defineProperty(window, "__E2E_TEST__", {
26
+ value: true,
27
+ writable: false,
28
+ configurable: false,
29
+ });
30
+ });
31
+ return page;
32
+ }
33
+ /** Close the browser and all its contexts. */
34
+ export async function closeBrowser(browser) {
35
+ await browser.close();
36
+ }
37
+ /** Extract browser options from RunConfig. */
38
+ export function toBrowserOptions(config) {
39
+ return {
40
+ headed: config.headed,
41
+ viewport: config.viewport,
42
+ };
43
+ }
44
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAgD,MAAM,YAAY,CAAA;AAQnF,0CAA0C;AAC1C,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAsB;IACzD,OAAO,QAAQ,CAAC,MAAM,CAAC;QACtB,QAAQ,EAAE,CAAC,MAAM,CAAC,MAAM;KACxB,CAAC,CAAA;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,OAAgB,EAChB,MAAsB;IAEtB,OAAO,OAAO,CAAC,UAAU,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,gBAAgB,EAAE;YACjB,YAAY,EAAE,MAAM;SACpB;KACD,CAAC,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAuB;IACvD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACpC,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE;QAC7B,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;YAC7C,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE,KAAK;SACnB,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IAClD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;AACtB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IACjD,OAAO;QACN,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KACzB,CAAA;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAA"}
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { Command } from "commander";
4
+ import { DEFAULTS } from "../types.js";
5
+ import { loadProjectConfig } from "../config.js";
6
+ import { createTraceLogger } from "../pilot/trace.js";
7
+ import { resolve } from "node:path";
8
+ import { glob } from "node:fs";
9
+ import { initGlobals } from "../globals.js";
10
+ import { runCommand, showPlanStatus } from "./run.js";
11
+ const program = new Command();
12
+ program
13
+ .name("greenlight")
14
+ .description("AI-driven E2E testing tool")
15
+ .version("0.1.0");
16
+ program
17
+ .command("run")
18
+ .description("Run test suites against a staging environment")
19
+ .argument("[suites...]", "paths to suite YAML files (overrides greenlight.yaml)")
20
+ .option("-t, --test <name>", "run only the test case matching this name")
21
+ .option("--base-url <url>", "override the suite base URL")
22
+ .option("-r, --reporter <format>", "output format: cli, json, or html")
23
+ .option("-o, --output <path>", "write report to file instead of stdout")
24
+ .option("--headed", "run browser in visible (headed) mode")
25
+ .option("-p, --parallel <n>", "number of test cases to run concurrently")
26
+ .option("--timeout <ms>", "per-step timeout in milliseconds")
27
+ .option("--model <model>", "LLM model identifier (e.g. anthropic/claude-sonnet-4)")
28
+ .option("--llm-base-url <url>", "base URL for the LLM API (override)")
29
+ .option("--provider <name>", "LLM provider: openrouter, openai, gemini, or claude")
30
+ .option("-d, --deployment <name>", "select a named deployment from greenlight.yaml")
31
+ .option("--debug", "enable verbose debug output", false)
32
+ .option("--trace", "log timestamped browser events for performance analysis", false)
33
+ .option("--discover", "force full discovery run, ignore cached plans", false)
34
+ .option("--on-drift <mode>", 'behavior on plan drift: "fail" or "rerun" (default: fail)')
35
+ .option("--plan-status", "show cached plan status for all test cases and exit", false)
36
+ .action(async (suitesArg, opts) => {
37
+ // Load project config (greenlight.yaml)
38
+ const projectConfig = await loadProjectConfig(opts.deployment);
39
+ // Resolve suite files: CLI args > greenlight.yaml > error
40
+ let suiteFiles;
41
+ if (suitesArg.length > 0) {
42
+ suiteFiles = suitesArg.map((s) => resolve(s));
43
+ }
44
+ else if (projectConfig?.suites) {
45
+ suiteFiles = projectConfig.suites.map((s) => resolve(s));
46
+ }
47
+ else {
48
+ console.error("No suite files specified. Provide them as arguments or in greenlight.yaml.");
49
+ process.exit(1);
50
+ }
51
+ // Build config: CLI flags > greenlight.yaml > built-in defaults
52
+ const config = {
53
+ suiteFiles,
54
+ testFilter: opts.test,
55
+ baseUrl: opts.baseUrl ?? projectConfig?.base_url,
56
+ reporter: parseReporter(opts.reporter ?? projectConfig?.reporter ?? DEFAULTS.reporter),
57
+ outputPath: opts.output ? resolve(opts.output) : undefined,
58
+ headed: opts.headed ?? projectConfig?.headed ?? DEFAULTS.headed,
59
+ parallel: opts.parallel
60
+ ? parseInt(opts.parallel, 10)
61
+ : (projectConfig?.parallel ?? DEFAULTS.parallel),
62
+ timeout: opts.timeout
63
+ ? parseInt(opts.timeout, 10)
64
+ : (projectConfig?.timeout ?? DEFAULTS.timeout),
65
+ viewport: projectConfig?.viewport
66
+ ? { ...projectConfig.viewport }
67
+ : { ...DEFAULTS.viewport },
68
+ model: opts.model ?? projectConfig?.model ?? DEFAULTS.model,
69
+ provider: parseProvider(opts.provider ??
70
+ projectConfig?.provider ??
71
+ DEFAULTS.provider),
72
+ llmBaseUrl: opts.llmBaseUrl ?? projectConfig?.llm_base_url,
73
+ discover: opts.discover,
74
+ onDrift: parseOnDrift(opts.onDrift ?? DEFAULTS.onDrift),
75
+ };
76
+ // Initialize global runtime state (debug, trace)
77
+ initGlobals({
78
+ debug: opts.debug,
79
+ trace: createTraceLogger(opts.trace),
80
+ });
81
+ // Resolve glob patterns in suite file paths
82
+ const resolvedFiles = await resolveGlobs(config.suiteFiles);
83
+ if (resolvedFiles.length === 0) {
84
+ console.error("No suite files found matching:", config.suiteFiles);
85
+ process.exit(1);
86
+ }
87
+ // --plan-status: show cache status and exit
88
+ if (opts.planStatus) {
89
+ await showPlanStatus(resolvedFiles, process.cwd(), config);
90
+ return;
91
+ }
92
+ await runCommand(config, resolvedFiles);
93
+ });
94
+ function parseProvider(value) {
95
+ if (value === "openrouter" ||
96
+ value === "openai" ||
97
+ value === "gemini" ||
98
+ value === "claude") {
99
+ return value;
100
+ }
101
+ console.error(`Invalid provider "${value}". Must be openrouter, openai, gemini, or claude.`);
102
+ process.exit(1);
103
+ }
104
+ function parseReporter(value) {
105
+ if (value === "cli" || value === "json" || value === "html") {
106
+ return value;
107
+ }
108
+ console.error(`Invalid reporter "${value}". Must be cli, json, or html.`);
109
+ process.exit(1);
110
+ }
111
+ function parseOnDrift(value) {
112
+ if (value === "fail" || value === "rerun") {
113
+ return value;
114
+ }
115
+ console.error(`Invalid --on-drift value "${value}". Must be "fail" or "rerun".`);
116
+ process.exit(1);
117
+ }
118
+ /** Expand glob patterns into concrete file paths. */
119
+ async function resolveGlobs(patterns) {
120
+ const files = [];
121
+ for (const pattern of patterns) {
122
+ // If pattern has no glob chars, treat as literal path
123
+ if (!pattern.includes("*")) {
124
+ files.push(pattern);
125
+ continue;
126
+ }
127
+ const matches = await new Promise((res, rej) => {
128
+ glob(pattern, (err, result) => {
129
+ if (err)
130
+ rej(err);
131
+ else
132
+ res(result);
133
+ });
134
+ });
135
+ files.push(...matches);
136
+ }
137
+ return files;
138
+ }
139
+ program.parse();
140
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { RunConfig } from "../types.js";
2
+ /** Show plan status for all test cases across all suites. */
3
+ export declare function showPlanStatus(suiteFiles: string[], cwd: string, config: RunConfig): Promise<void>;
4
+ /**
5
+ * Run all test suites according to the resolved configuration.
6
+ * This is the main test execution entry point called by the CLI.
7
+ */
8
+ export declare function runCommand(config: RunConfig, resolvedFiles: string[]): Promise<void>;
9
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAuC5C,6DAA6D;AAC7D,wBAAsB,cAAc,CACnC,UAAU,EAAE,MAAM,EAAE,EACpB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC,CAgDf;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC/B,MAAM,EAAE,SAAS,EACjB,aAAa,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,IAAI,CAAC,CAuQf"}