@gherkle/runner 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.
- package/README.md +482 -0
- package/bin/gherkle-runner.js +111 -0
- package/dist/__tests__/agent-executor-selection.test.d.ts +15 -0
- package/dist/__tests__/agent-executor-selection.test.d.ts.map +1 -0
- package/dist/__tests__/agent-executor-selection.test.js +192 -0
- package/dist/__tests__/agent-executor-selection.test.js.map +1 -0
- package/dist/__tests__/envelope-mapper.test.d.ts +17 -0
- package/dist/__tests__/envelope-mapper.test.d.ts.map +1 -0
- package/dist/__tests__/envelope-mapper.test.js +217 -0
- package/dist/__tests__/envelope-mapper.test.js.map +1 -0
- package/dist/__tests__/gherkin-parser.test.d.ts +2 -0
- package/dist/__tests__/gherkin-parser.test.d.ts.map +1 -0
- package/dist/__tests__/gherkin-parser.test.js +375 -0
- package/dist/__tests__/gherkin-parser.test.js.map +1 -0
- package/dist/__tests__/integration-fork.test.d.ts +16 -0
- package/dist/__tests__/integration-fork.test.d.ts.map +1 -0
- package/dist/__tests__/integration-fork.test.js +141 -0
- package/dist/__tests__/integration-fork.test.js.map +1 -0
- package/dist/__tests__/tmpdir-materialization.test.d.ts +18 -0
- package/dist/__tests__/tmpdir-materialization.test.d.ts.map +1 -0
- package/dist/__tests__/tmpdir-materialization.test.js +177 -0
- package/dist/__tests__/tmpdir-materialization.test.js.map +1 -0
- package/dist/agent.d.ts +71 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +312 -0
- package/dist/agent.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +165 -0
- package/dist/config.js.map +1 -0
- package/dist/frameworks/cucumber-subprocess.d.ts +129 -0
- package/dist/frameworks/cucumber-subprocess.d.ts.map +1 -0
- package/dist/frameworks/cucumber-subprocess.js +469 -0
- package/dist/frameworks/cucumber-subprocess.js.map +1 -0
- package/dist/frameworks/playwright.d.ts +22 -0
- package/dist/frameworks/playwright.d.ts.map +1 -0
- package/dist/frameworks/playwright.js +234 -0
- package/dist/frameworks/playwright.js.map +1 -0
- package/dist/gherkin-parser.d.ts +22 -0
- package/dist/gherkin-parser.d.ts.map +1 -0
- package/dist/gherkin-parser.js +148 -0
- package/dist/gherkin-parser.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/step-registry.d.ts +30 -0
- package/dist/step-registry.d.ts.map +1 -0
- package/dist/step-registry.js +160 -0
- package/dist/step-registry.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/workers/cucumber-worker.d.ts +29 -0
- package/dist/workers/cucumber-worker.d.ts.map +1 -0
- package/dist/workers/cucumber-worker.js +191 -0
- package/dist/workers/cucumber-worker.js.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# @gherkle/runner
|
|
2
|
+
|
|
3
|
+
Test runner agent for Gherkle. Connects to the Gherkle platform over WebSocket, receives Gherkin feature files, executes them against a configured test framework, and streams results back in real time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Option 1 ā npm (recommended)
|
|
8
|
+
|
|
9
|
+
Global install gives you the `gherkle-runner` CLI:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @gherkle/runner @playwright/test playwright-core
|
|
13
|
+
npx playwright install chromium
|
|
14
|
+
gherkle-runner start --token ghr_xxxxx --url https://gherkle-runner.osita.ai
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or one-shot via `npx` (no global install):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @playwright/test playwright-core
|
|
21
|
+
npx playwright install chromium
|
|
22
|
+
npx @gherkle/runner start --token ghr_xxxxx --url https://gherkle-runner.osita.ai
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@playwright/test` and `playwright-core` are peer dependencies ā install
|
|
26
|
+
them alongside the runner. AI-generated step definitions import `expect`
|
|
27
|
+
from `@playwright/test`, and the bundled Cucumber executor drives
|
|
28
|
+
`playwright-core` for browser automation.
|
|
29
|
+
|
|
30
|
+
### Option 2 ā Docker
|
|
31
|
+
|
|
32
|
+
A prebuilt image with browsers preinstalled is available at the
|
|
33
|
+
monorepo's `packages/runner/Dockerfile.browsers`. Build locally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/rvasqz86/gherkle-monorepo.git
|
|
37
|
+
cd gherkle-monorepo
|
|
38
|
+
docker build -t gherkle-runner -f packages/runner/Dockerfile.browsers .
|
|
39
|
+
docker run --rm \
|
|
40
|
+
-e GHERKLE_API_URL=https://gherkle-runner.osita.ai \
|
|
41
|
+
-e GHERKLE_TOKEN=ghr_xxxxx \
|
|
42
|
+
gherkle-runner
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Option 3 ā From source (for development)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/rvasqz86/gherkle-monorepo.git
|
|
49
|
+
cd gherkle-monorepo
|
|
50
|
+
npm install
|
|
51
|
+
npm run build --workspace=@gherkle/runner
|
|
52
|
+
npx playwright install chromium
|
|
53
|
+
node packages/runner/bin/gherkle-runner.js start \
|
|
54
|
+
--token ghr_xxxxx \
|
|
55
|
+
--url https://gherkle-runner.osita.ai
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How runs work
|
|
59
|
+
|
|
60
|
+
When you click **Run** in Gherkle the server pushes the feature file AND the
|
|
61
|
+
saved AI-generated step definitions (`step_definition_files` table) to your
|
|
62
|
+
runner over the existing WebSocket. The runner materializes a self-contained
|
|
63
|
+
temp project on disk, forks `@cucumber/cucumber`, and streams pass/fail
|
|
64
|
+
results back. **No step-definition code needs to exist on the runner machine.**
|
|
65
|
+
|
|
66
|
+
For the BYO workflow (your own step-defs already committed to disk), set
|
|
67
|
+
`stepDefinitionsPath` in `gherkle-runner.config.js` and dispatch with a
|
|
68
|
+
project that has no saved step-defs ā the runner falls back to the legacy
|
|
69
|
+
Playwright executor against your local files.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### 1. Generate a config file
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
gherkle-runner init
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This creates `gherkle-runner.config.js` in the current directory.
|
|
80
|
+
|
|
81
|
+
### 2. Set your runner token
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
export GHERKLE_TOKEN=ghr_xxxxx
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Start the runner
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
gherkle-runner start
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The runner connects to Gherkle and waits for test runs. It automatically reconnects if the connection drops.
|
|
94
|
+
|
|
95
|
+
## CLI Reference
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
gherkle-runner <command> [options]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `start`
|
|
102
|
+
|
|
103
|
+
Start the runner agent and connect to Gherkle.
|
|
104
|
+
|
|
105
|
+
| Option | Description |
|
|
106
|
+
| ----------------------------- | ------------------------------------------------------ |
|
|
107
|
+
| `-t, --token <token>` | Runner token (overrides `GHERKLE_TOKEN` env var) |
|
|
108
|
+
| `-n, --name <name>` | Runner name |
|
|
109
|
+
| `-u, --url <url>` | Gherkle API URL |
|
|
110
|
+
| `-f, --framework <framework>` | Test framework: `playwright`, `cypress`, or `cucumber` |
|
|
111
|
+
| `--headless` | Run browsers in headless mode (default: `true`) |
|
|
112
|
+
| `--no-headless` | Run browsers with visible UI |
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
gherkle-runner start --token ghr_xxxxx --name ci-runner --framework playwright
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `init`
|
|
119
|
+
|
|
120
|
+
Generate a sample `gherkle-runner.config.js` in the current directory. Fails if one already exists.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
gherkle-runner init
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `status`
|
|
127
|
+
|
|
128
|
+
Print runner version, config file detection, and token status.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
gherkle-runner status
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
Configuration is resolved by merging sources in this order (later wins):
|
|
137
|
+
|
|
138
|
+
1. Built-in defaults
|
|
139
|
+
2. Config file
|
|
140
|
+
3. Environment variables
|
|
141
|
+
4. CLI options
|
|
142
|
+
|
|
143
|
+
### Config File
|
|
144
|
+
|
|
145
|
+
The runner looks for config files in the working directory, checking these names in order:
|
|
146
|
+
|
|
147
|
+
- `gherkle-runner.config.js`
|
|
148
|
+
- `gherkle-runner.config.mjs`
|
|
149
|
+
- `gherkle-runner.config.ts`
|
|
150
|
+
- `.gherklerc.js`
|
|
151
|
+
- `.gherklerc.json`
|
|
152
|
+
|
|
153
|
+
Example config:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
// gherkle-runner.config.js
|
|
157
|
+
module.exports = {
|
|
158
|
+
// Connection (required)
|
|
159
|
+
token: process.env.GHERKLE_TOKEN,
|
|
160
|
+
|
|
161
|
+
// Runner identification
|
|
162
|
+
name: "my-runner",
|
|
163
|
+
labels: ["local", "dev"],
|
|
164
|
+
|
|
165
|
+
// Test framework
|
|
166
|
+
framework: "playwright", // 'playwright' | 'cypress' | 'cucumber'
|
|
167
|
+
|
|
168
|
+
// Paths
|
|
169
|
+
featuresPath: "./features",
|
|
170
|
+
stepDefinitionsPath: "./steps",
|
|
171
|
+
supportPath: "./support",
|
|
172
|
+
|
|
173
|
+
// Browser configuration (E2E only)
|
|
174
|
+
browsers: ["chromium"],
|
|
175
|
+
headless: true,
|
|
176
|
+
|
|
177
|
+
// Timeouts
|
|
178
|
+
testTimeout: 30000,
|
|
179
|
+
stepTimeout: 10000,
|
|
180
|
+
|
|
181
|
+
// Artifacts
|
|
182
|
+
screenshots: "on-failure", // 'always' | 'on-failure' | 'never'
|
|
183
|
+
video: "never", // 'always' | 'on-failure' | 'never'
|
|
184
|
+
|
|
185
|
+
// Environment
|
|
186
|
+
baseUrl: process.env.BASE_URL || "http://localhost:3000",
|
|
187
|
+
env: {
|
|
188
|
+
API_URL: process.env.API_URL,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Environment Variables
|
|
194
|
+
|
|
195
|
+
| Variable | Maps to |
|
|
196
|
+
| --------------------- | -------- |
|
|
197
|
+
| `GHERKLE_TOKEN` | `token` |
|
|
198
|
+
| `GHERKLE_API_URL` | `apiUrl` |
|
|
199
|
+
| `GHERKLE_RUNNER_NAME` | `name` |
|
|
200
|
+
|
|
201
|
+
### Config Options Reference
|
|
202
|
+
|
|
203
|
+
| Option | Type | Default | Description |
|
|
204
|
+
| --------------------- | ---------- | ------------------------- | ------------------------------------------------------------ |
|
|
205
|
+
| `token` | `string` | **required** | Runner authentication token |
|
|
206
|
+
| `apiUrl` | `string` | `https://app.gherkle.com` | Gherkle platform URL |
|
|
207
|
+
| `name` | `string` | `gherkle-runner` | Display name for this runner |
|
|
208
|
+
| `labels` | `string[]` | `[]` | Labels for filtering/routing runs to this runner |
|
|
209
|
+
| `framework` | `string` | `playwright` | Test framework: `playwright`, `cypress`, or `cucumber` |
|
|
210
|
+
| `featuresPath` | `string` | `./features` | Directory containing `.feature` files |
|
|
211
|
+
| `stepDefinitionsPath` | `string` | `./steps` | Directory containing step definition files |
|
|
212
|
+
| `supportPath` | `string` | `./support` | Directory containing support/helper files |
|
|
213
|
+
| `browsers` | `string[]` | `['chromium']` | Browsers to use: `chromium`, `firefox`, `webkit` |
|
|
214
|
+
| `headless` | `boolean` | `true` | Run browsers without UI |
|
|
215
|
+
| `testTimeout` | `number` | `30000` | Max duration per test scenario (ms) |
|
|
216
|
+
| `stepTimeout` | `number` | `10000` | Max duration per step (ms) |
|
|
217
|
+
| `screenshots` | `string` | `on-failure` | When to capture screenshots: `always`, `on-failure`, `never` |
|
|
218
|
+
| `video` | `string` | `never` | When to record video: `always`, `on-failure`, `never` |
|
|
219
|
+
| `baseUrl` | `string` | `undefined` | Base URL for the application under test |
|
|
220
|
+
| `env` | `object` | `{}` | Key-value pairs passed to step definitions as `context.env` |
|
|
221
|
+
|
|
222
|
+
## Writing Step Definitions
|
|
223
|
+
|
|
224
|
+
Step definition files are loaded from `stepDefinitionsPath`. The runner scans recursively for files matching:
|
|
225
|
+
|
|
226
|
+
- `*.steps.ts` / `*.steps.js`
|
|
227
|
+
- `*.step.ts` / `*.step.js`
|
|
228
|
+
- `*_steps.ts` / `*_steps.js`
|
|
229
|
+
|
|
230
|
+
### Step File Format
|
|
231
|
+
|
|
232
|
+
A step file exports a function that receives registration helpers:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
// steps/login.steps.js
|
|
236
|
+
module.exports = function ({ Given, When, Then }) {
|
|
237
|
+
Given(/the user is on the login page/, async (context) => {
|
|
238
|
+
await context.page.goto(`${context.baseUrl}/login`);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
When(
|
|
242
|
+
/the user enters "(.*)" and "(.*)"/,
|
|
243
|
+
async (context, username, password) => {
|
|
244
|
+
await context.page.fill("#username", username);
|
|
245
|
+
await context.page.fill("#password", password);
|
|
246
|
+
await context.page.click('button[type="submit"]');
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
Then(/the user sees the dashboard/, async (context) => {
|
|
251
|
+
await context.page.waitForURL("**/dashboard");
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Step Context
|
|
257
|
+
|
|
258
|
+
Every step function receives a `StepContext` object as its first argument:
|
|
259
|
+
|
|
260
|
+
| Property | Type | Description |
|
|
261
|
+
| ----------- | --------------------------- | -------------------------------------------------- |
|
|
262
|
+
| `page` | `playwright.Page` | Playwright Page instance for the current scenario |
|
|
263
|
+
| `context` | `playwright.BrowserContext` | Playwright BrowserContext for the current scenario |
|
|
264
|
+
| `baseUrl` | `string \| undefined` | The configured `baseUrl` |
|
|
265
|
+
| `env` | `Record<string, string>` | Environment variables from config |
|
|
266
|
+
| `dataTable` | `string[][] \| undefined` | Data table attached to the current step |
|
|
267
|
+
| `docString` | `string \| undefined` | Doc string attached to the current step |
|
|
268
|
+
|
|
269
|
+
Capture groups from the step pattern regex are passed as additional string arguments after the context.
|
|
270
|
+
|
|
271
|
+
### Keyword Matching
|
|
272
|
+
|
|
273
|
+
Steps registered with `Given`, `When`, or `Then` are matched by keyword first. Steps using `And` or `But` in the feature file inherit their effective keyword from the preceding step. If no keyword-specific match is found, the runner falls back to matching against all registered steps regardless of keyword.
|
|
274
|
+
|
|
275
|
+
## Gherkin Parser
|
|
276
|
+
|
|
277
|
+
The runner includes a built-in Gherkin parser (`@cucumber/gherkin`) that supports:
|
|
278
|
+
|
|
279
|
+
- Feature descriptions and tags
|
|
280
|
+
- Scenarios with Given/When/Then/And/But steps
|
|
281
|
+
- Background sections (run before each scenario)
|
|
282
|
+
- Scenario Outlines with Examples tables (expanded into individual scenarios)
|
|
283
|
+
- Data Tables on steps
|
|
284
|
+
- Doc Strings on steps
|
|
285
|
+
- Multiple Examples blocks per outline
|
|
286
|
+
- Tags at feature, scenario, and examples level
|
|
287
|
+
|
|
288
|
+
## Architecture
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
bin/gherkle-runner.js CLI entrypoint (commander)
|
|
292
|
+
src/
|
|
293
|
+
index.ts Public API exports
|
|
294
|
+
types.ts Type definitions and WebSocket message types
|
|
295
|
+
config.ts Config loading, merging, validation
|
|
296
|
+
agent.ts GherkleAgent - WebSocket client, run orchestration
|
|
297
|
+
step-registry.ts Step definition registry and matching
|
|
298
|
+
gherkin-parser.ts Gherkin AST parsing
|
|
299
|
+
frameworks/
|
|
300
|
+
playwright.ts Playwright executor (browser launch, scenario execution)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### How It Works
|
|
304
|
+
|
|
305
|
+
1. **Connect** -- `GherkleAgent` opens a WebSocket connection to the Gherkle platform, authenticating with the runner token.
|
|
306
|
+
2. **Wait** -- The agent sends heartbeats every 30 seconds and waits for an `execute` message.
|
|
307
|
+
3. **Execute** -- When a run is dispatched, the agent parses the received Gherkin feature files, loads step definitions, launches a browser, and runs each scenario.
|
|
308
|
+
4. **Stream** -- Step and scenario results are sent back to Gherkle in real time over the WebSocket (`scenario:started`, `step:completed`, `scenario:completed`).
|
|
309
|
+
5. **Artifacts** -- On step failure (when `screenshots` is not `never`), a full-page screenshot is captured and reported as an artifact. Screenshots are saved to `.gherkle-artifacts/` in the working directory.
|
|
310
|
+
6. **Complete** -- A `run:completed` message with the full summary is sent when all features finish.
|
|
311
|
+
|
|
312
|
+
The agent automatically reconnects after 5 seconds if the WebSocket connection drops.
|
|
313
|
+
|
|
314
|
+
## Programmatic API
|
|
315
|
+
|
|
316
|
+
The package exports the agent and config loader for use in custom tooling:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { GherkleAgent, loadConfig } from "@gherkle/runner";
|
|
320
|
+
|
|
321
|
+
const config = loadConfig({
|
|
322
|
+
token: "ghr_xxxxx",
|
|
323
|
+
framework: "playwright",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const agent = new GherkleAgent(config);
|
|
327
|
+
|
|
328
|
+
// Handle shutdown
|
|
329
|
+
process.on("SIGINT", () => agent.stop());
|
|
330
|
+
|
|
331
|
+
await agent.start();
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Exports
|
|
335
|
+
|
|
336
|
+
| Export | Description |
|
|
337
|
+
| ------------------------ | ------------------------------------------------------------------------------------ |
|
|
338
|
+
| `GherkleAgent` | Main agent class. Call `start()` to connect, `stop()` to disconnect. |
|
|
339
|
+
| `loadConfig(overrides?)` | Load and merge config from file, env, and provided overrides. Returns `AgentConfig`. |
|
|
340
|
+
| `AgentConfig` | Configuration interface |
|
|
341
|
+
| `RunnerStatus` | `'online' \| 'offline' \| 'busy' \| 'error'` |
|
|
342
|
+
| `TestStatus` | `'pending' \| 'running' \| 'passed' \| 'failed' \| 'skipped'` |
|
|
343
|
+
| `Framework` | `'playwright' \| 'cypress' \| 'cucumber'` |
|
|
344
|
+
| `StepResult` | Result of a single step execution |
|
|
345
|
+
| `ScenarioResult` | Result of a scenario including all steps and artifacts |
|
|
346
|
+
| `RunSummary` | Aggregate pass/fail/skip counts and duration |
|
|
347
|
+
| `FeatureContent` | Feature file id, name, path, and raw content |
|
|
348
|
+
| `TestConfig` | Framework and path configuration for a test run |
|
|
349
|
+
| `ArtifactInfo` | Metadata for a captured artifact (screenshot, video, trace, log) |
|
|
350
|
+
| `RunnerMessage` | Union type of all messages sent from runner to server |
|
|
351
|
+
| `ServerMessage` | Union type of all messages sent from server to runner |
|
|
352
|
+
|
|
353
|
+
## Docker
|
|
354
|
+
|
|
355
|
+
Two Dockerfiles are provided depending on your workload:
|
|
356
|
+
|
|
357
|
+
| File | Base image | Size | Use case |
|
|
358
|
+
| --------------------- | ------------------------------ | ------- | ----------------------------------------------------------------- |
|
|
359
|
+
| `Dockerfile` | `node:22-slim` | ~200 MB | Step definitions, assertions, API tests, any non-browser workload |
|
|
360
|
+
| `Dockerfile.browsers` | `mcr.microsoft.com/playwright` | ~2 GB | E2E tests requiring Chromium, Firefox, or WebKit |
|
|
361
|
+
|
|
362
|
+
Use the default `Dockerfile` unless your step definitions launch a browser.
|
|
363
|
+
|
|
364
|
+
### Build
|
|
365
|
+
|
|
366
|
+
Both Dockerfiles must be built from the **monorepo root** so the workspace lockfile is available:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Standard image (step definitions + assertions, no browsers)
|
|
370
|
+
docker build -t gherkle-runner -f packages/runner/Dockerfile .
|
|
371
|
+
|
|
372
|
+
# With Playwright browsers for E2E
|
|
373
|
+
docker build -t gherkle-runner-browsers -f packages/runner/Dockerfile.browsers .
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Run
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
docker run --rm \
|
|
380
|
+
-e GHERKLE_TOKEN=ghr_xxxxx \
|
|
381
|
+
gherkle-runner
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Mount step definitions and features
|
|
385
|
+
|
|
386
|
+
Bind-mount your project directories so the runner can find your test code:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
docker run --rm \
|
|
390
|
+
-e GHERKLE_TOKEN=ghr_xxxxx \
|
|
391
|
+
-v $(pwd)/features:/app/features \
|
|
392
|
+
-v $(pwd)/steps:/app/steps \
|
|
393
|
+
-v $(pwd)/support:/app/support \
|
|
394
|
+
gherkle-runner
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Pass CLI options
|
|
398
|
+
|
|
399
|
+
Arguments after the image name are forwarded to `gherkle-runner start`:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
docker run --rm \
|
|
403
|
+
-e GHERKLE_TOKEN=ghr_xxxxx \
|
|
404
|
+
gherkle-runner start --name ci-runner --framework cucumber
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Use a config file
|
|
408
|
+
|
|
409
|
+
Mount a config file into `/app`:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
docker run --rm \
|
|
413
|
+
-v $(pwd)/gherkle-runner.config.js:/app/gherkle-runner.config.js \
|
|
414
|
+
-v $(pwd)/features:/app/features \
|
|
415
|
+
-v $(pwd)/steps:/app/steps \
|
|
416
|
+
gherkle-runner
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Docker Compose
|
|
420
|
+
|
|
421
|
+
```yaml
|
|
422
|
+
services:
|
|
423
|
+
# Headless step execution (API tests, assertions, etc.)
|
|
424
|
+
gherkle-runner:
|
|
425
|
+
build:
|
|
426
|
+
context: .
|
|
427
|
+
dockerfile: packages/runner/Dockerfile
|
|
428
|
+
environment:
|
|
429
|
+
- GHERKLE_TOKEN=${GHERKLE_TOKEN}
|
|
430
|
+
- GHERKLE_RUNNER_NAME=docker-runner
|
|
431
|
+
volumes:
|
|
432
|
+
- ./features:/app/features
|
|
433
|
+
- ./steps:/app/steps
|
|
434
|
+
- ./support:/app/support
|
|
435
|
+
restart: unless-stopped
|
|
436
|
+
|
|
437
|
+
# E2E variant with Playwright browsers
|
|
438
|
+
gherkle-runner-e2e:
|
|
439
|
+
build:
|
|
440
|
+
context: .
|
|
441
|
+
dockerfile: packages/runner/Dockerfile.browsers
|
|
442
|
+
environment:
|
|
443
|
+
- GHERKLE_TOKEN=${GHERKLE_TOKEN}
|
|
444
|
+
- GHERKLE_RUNNER_NAME=docker-e2e-runner
|
|
445
|
+
volumes:
|
|
446
|
+
- ./features:/app/features
|
|
447
|
+
- ./steps:/app/steps
|
|
448
|
+
- ./support:/app/support
|
|
449
|
+
restart: unless-stopped
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Environment variables
|
|
453
|
+
|
|
454
|
+
All [configuration environment variables](#environment-variables) work inside the container. Both images run as a non-root user (`gherkle` / `pwuser`) by default.
|
|
455
|
+
|
|
456
|
+
## Requirements
|
|
457
|
+
|
|
458
|
+
- Node.js >= 18
|
|
459
|
+
- Playwright browsers installed (for E2E execution): `npx playwright install`
|
|
460
|
+
|
|
461
|
+
## Development
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
# Build
|
|
465
|
+
npm run build
|
|
466
|
+
|
|
467
|
+
# Watch mode
|
|
468
|
+
npm run dev
|
|
469
|
+
|
|
470
|
+
# Run tests
|
|
471
|
+
npm test
|
|
472
|
+
|
|
473
|
+
# Run tests in watch mode
|
|
474
|
+
npm run test:watch
|
|
475
|
+
|
|
476
|
+
# Lint
|
|
477
|
+
npm run lint
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## License
|
|
481
|
+
|
|
482
|
+
MIT
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gherkle Runner CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* gherkle-runner start --token=ghr_xxxxx
|
|
8
|
+
* gherkle-runner init
|
|
9
|
+
* gherkle-runner status
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { program } = require('commander');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
// Try to load the compiled version, fall back to ts-node for development
|
|
17
|
+
let GherkleAgent, loadConfig, generateSampleConfig;
|
|
18
|
+
try {
|
|
19
|
+
const dist = require('../dist/index.js');
|
|
20
|
+
GherkleAgent = dist.GherkleAgent;
|
|
21
|
+
loadConfig = dist.loadConfig;
|
|
22
|
+
generateSampleConfig = dist.generateSampleConfig;
|
|
23
|
+
} catch {
|
|
24
|
+
console.log('Note: Running in development mode');
|
|
25
|
+
// In development, you'd use ts-node or similar
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.name('gherkle-runner')
|
|
31
|
+
.description('Test runner agent for Gherkle')
|
|
32
|
+
.version('0.1.0');
|
|
33
|
+
|
|
34
|
+
// Start command
|
|
35
|
+
program
|
|
36
|
+
.command('start')
|
|
37
|
+
.description('Start the runner agent and connect to Gherkle')
|
|
38
|
+
.option('-t, --token <token>', 'Runner token (or use GHERKLE_TOKEN env var)')
|
|
39
|
+
.option('-n, --name <name>', 'Runner name')
|
|
40
|
+
.option('-u, --url <url>', 'Gherkle API URL')
|
|
41
|
+
.option('-f, --framework <framework>', 'Test framework (playwright, cypress, cucumber)')
|
|
42
|
+
.option('--headless', 'Run browsers in headless mode', true)
|
|
43
|
+
.option('--no-headless', 'Run browsers with UI')
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
try {
|
|
46
|
+
const config = loadConfig({
|
|
47
|
+
token: options.token,
|
|
48
|
+
name: options.name,
|
|
49
|
+
apiUrl: options.url,
|
|
50
|
+
framework: options.framework,
|
|
51
|
+
headless: options.headless,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const agent = new GherkleAgent(config);
|
|
55
|
+
|
|
56
|
+
// Handle shutdown signals
|
|
57
|
+
process.on('SIGINT', async () => {
|
|
58
|
+
console.log('\nReceived SIGINT, shutting down...');
|
|
59
|
+
await agent.stop();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
process.on('SIGTERM', async () => {
|
|
64
|
+
console.log('\nReceived SIGTERM, shutting down...');
|
|
65
|
+
await agent.stop();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await agent.start();
|
|
70
|
+
|
|
71
|
+
// Keep the process alive
|
|
72
|
+
console.log('\nš Waiting for test runs... (Ctrl+C to stop)\n');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('Error:', err.message);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Init command
|
|
80
|
+
program
|
|
81
|
+
.command('init')
|
|
82
|
+
.description('Generate a sample configuration file')
|
|
83
|
+
.action(() => {
|
|
84
|
+
const configPath = path.join(process.cwd(), 'gherkle-runner.config.js');
|
|
85
|
+
|
|
86
|
+
if (fs.existsSync(configPath)) {
|
|
87
|
+
console.error('Error: Configuration file already exists');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(configPath, generateSampleConfig());
|
|
92
|
+
console.log('ā
Created gherkle-runner.config.js');
|
|
93
|
+
console.log('\nNext steps:');
|
|
94
|
+
console.log('1. Edit the config file with your settings');
|
|
95
|
+
console.log('2. Set GHERKLE_TOKEN environment variable');
|
|
96
|
+
console.log('3. Run: gherkle-runner start');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Status command
|
|
100
|
+
program
|
|
101
|
+
.command('status')
|
|
102
|
+
.description('Check runner status')
|
|
103
|
+
.action(() => {
|
|
104
|
+
console.log('Gherkle Runner Status');
|
|
105
|
+
console.log('---------------------');
|
|
106
|
+
console.log('Version: 0.1.0');
|
|
107
|
+
console.log('Config file:', fs.existsSync('gherkle-runner.config.js') ? 'Found' : 'Not found');
|
|
108
|
+
console.log('Token:', process.env.GHERKLE_TOKEN ? 'Set (from env)' : 'Not set');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
program.parse();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for GherkleAgent.executeRun executor selection logic.
|
|
3
|
+
*
|
|
4
|
+
* The agent branches based on the incoming 'execute' WebSocket message:
|
|
5
|
+
* stepDefinitions non-empty ā CucumberSubprocessExecutor.executeAll
|
|
6
|
+
* empty + stepDefinitionsPath set ā PlaywrightExecutor.execute (BYO)
|
|
7
|
+
* empty + no path ā throw "No step definitions"
|
|
8
|
+
*
|
|
9
|
+
* Also covers:
|
|
10
|
+
* - Busy-guard (refuses 2nd 'execute' while first is in flight)
|
|
11
|
+
* - cancelRun routes to currentCucumberExecutor.cancel()
|
|
12
|
+
* - cancelRun for non-matching runId is a no-op
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=agent-executor-selection.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-executor-selection.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agent-executor-selection.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|