@nouhouari/conductor-e2e 0.0.0-stage → 0.1.9

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 (101) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/LICENSE +21 -0
  3. package/README.md +196 -2
  4. package/config/environments/default.ts +41 -0
  5. package/config/environments/dev.ts +16 -0
  6. package/config/environments/staging.ts +15 -0
  7. package/config/index.ts +70 -0
  8. package/config/types.ts +94 -0
  9. package/dist/config/environments/default.d.ts +3 -0
  10. package/dist/config/environments/default.d.ts.map +1 -0
  11. package/dist/config/environments/default.js +40 -0
  12. package/dist/config/environments/default.js.map +1 -0
  13. package/dist/config/environments/dev.d.ts +3 -0
  14. package/dist/config/environments/dev.d.ts.map +1 -0
  15. package/dist/config/environments/dev.js +18 -0
  16. package/dist/config/environments/dev.js.map +1 -0
  17. package/dist/config/environments/staging.d.ts +3 -0
  18. package/dist/config/environments/staging.d.ts.map +1 -0
  19. package/dist/config/environments/staging.js +17 -0
  20. package/dist/config/environments/staging.js.map +1 -0
  21. package/dist/config/index.d.ts +7 -0
  22. package/dist/config/index.d.ts.map +1 -0
  23. package/dist/config/index.js +81 -0
  24. package/dist/config/index.js.map +1 -0
  25. package/dist/config/types.d.ts +93 -0
  26. package/dist/config/types.d.ts.map +1 -0
  27. package/dist/config/types.js +3 -0
  28. package/dist/config/types.js.map +1 -0
  29. package/dist/src/drivers/ApiDriver.d.ts +16 -0
  30. package/dist/src/drivers/ApiDriver.d.ts.map +1 -0
  31. package/dist/src/drivers/ApiDriver.js +48 -0
  32. package/dist/src/drivers/ApiDriver.js.map +1 -0
  33. package/dist/src/drivers/DatabaseDriver.d.ts +12 -0
  34. package/dist/src/drivers/DatabaseDriver.d.ts.map +1 -0
  35. package/dist/src/drivers/DatabaseDriver.js +15 -0
  36. package/dist/src/drivers/DatabaseDriver.js.map +1 -0
  37. package/dist/src/drivers/FlutterDesktopDriver.d.ts +139 -0
  38. package/dist/src/drivers/FlutterDesktopDriver.d.ts.map +1 -0
  39. package/dist/src/drivers/FlutterDesktopDriver.js +445 -0
  40. package/dist/src/drivers/FlutterDesktopDriver.js.map +1 -0
  41. package/dist/src/drivers/MaestroDriver.d.ts +19 -0
  42. package/dist/src/drivers/MaestroDriver.d.ts.map +1 -0
  43. package/dist/src/drivers/MaestroDriver.js +144 -0
  44. package/dist/src/drivers/MaestroDriver.js.map +1 -0
  45. package/dist/src/drivers/WebDriver.d.ts +21 -0
  46. package/dist/src/drivers/WebDriver.d.ts.map +1 -0
  47. package/dist/src/drivers/WebDriver.js +57 -0
  48. package/dist/src/drivers/WebDriver.js.map +1 -0
  49. package/dist/src/hooks/browser.hooks.d.ts +2 -0
  50. package/dist/src/hooks/browser.hooks.d.ts.map +1 -0
  51. package/dist/src/hooks/browser.hooks.js +15 -0
  52. package/dist/src/hooks/browser.hooks.js.map +1 -0
  53. package/dist/src/hooks/database.hooks.d.ts +2 -0
  54. package/dist/src/hooks/database.hooks.d.ts.map +1 -0
  55. package/dist/src/hooks/database.hooks.js +11 -0
  56. package/dist/src/hooks/database.hooks.js.map +1 -0
  57. package/dist/src/hooks/desktop.hooks.d.ts +2 -0
  58. package/dist/src/hooks/desktop.hooks.d.ts.map +1 -0
  59. package/dist/src/hooks/desktop.hooks.js +15 -0
  60. package/dist/src/hooks/desktop.hooks.js.map +1 -0
  61. package/dist/src/hooks/flutterDesktop.hooks.d.ts +2 -0
  62. package/dist/src/hooks/flutterDesktop.hooks.d.ts.map +1 -0
  63. package/dist/src/hooks/flutterDesktop.hooks.js +26 -0
  64. package/dist/src/hooks/flutterDesktop.hooks.js.map +1 -0
  65. package/dist/src/hooks/index.d.ts +6 -0
  66. package/dist/src/hooks/index.d.ts.map +1 -0
  67. package/dist/src/hooks/index.js +25 -0
  68. package/dist/src/hooks/index.js.map +1 -0
  69. package/dist/src/hooks/maestro.hooks.d.ts +2 -0
  70. package/dist/src/hooks/maestro.hooks.d.ts.map +1 -0
  71. package/dist/src/hooks/maestro.hooks.js +8 -0
  72. package/dist/src/hooks/maestro.hooks.js.map +1 -0
  73. package/dist/src/index.d.ts +21 -0
  74. package/dist/src/index.d.ts.map +1 -0
  75. package/dist/src/index.js +30 -0
  76. package/dist/src/index.js.map +1 -0
  77. package/dist/src/pages/BasePage.d.ts +11 -0
  78. package/dist/src/pages/BasePage.d.ts.map +1 -0
  79. package/dist/src/pages/BasePage.js +22 -0
  80. package/dist/src/pages/BasePage.js.map +1 -0
  81. package/dist/src/scenarios/RemoteScenarioFetcher.d.ts +51 -0
  82. package/dist/src/scenarios/RemoteScenarioFetcher.d.ts.map +1 -0
  83. package/dist/src/scenarios/RemoteScenarioFetcher.js +162 -0
  84. package/dist/src/scenarios/RemoteScenarioFetcher.js.map +1 -0
  85. package/dist/src/scenarios/cli.d.ts +3 -0
  86. package/dist/src/scenarios/cli.d.ts.map +1 -0
  87. package/dist/src/scenarios/cli.js +26 -0
  88. package/dist/src/scenarios/cli.js.map +1 -0
  89. package/dist/src/support/logger.d.ts +3 -0
  90. package/dist/src/support/logger.d.ts.map +1 -0
  91. package/dist/src/support/logger.js +16 -0
  92. package/dist/src/support/logger.js.map +1 -0
  93. package/dist/src/support/retry.d.ts +7 -0
  94. package/dist/src/support/retry.d.ts.map +1 -0
  95. package/dist/src/support/retry.js +21 -0
  96. package/dist/src/support/retry.js.map +1 -0
  97. package/dist/src/world/ConductorWorld.d.ts +39 -0
  98. package/dist/src/world/ConductorWorld.d.ts.map +1 -0
  99. package/dist/src/world/ConductorWorld.js +112 -0
  100. package/dist/src/world/ConductorWorld.js.map +1 -0
  101. package/package.json +81 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,237 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+
11
+ - **`release-java.yml` no longer fails after a successful Central publish** — bumped `central-publishing-maven-plugin` 0.7.0 → 0.11.0. 0.7.0 could not parse the `warnings` field the Central Portal now returns while polling deployment status, so the job failed even though `conductor-java 0.2.0` was published.
12
+
13
+ ## [conductor-e2e 0.1.9] — 2026-10-02
14
+
15
+ Docs-only release so the npmjs.com package page shows the current install instructions.
16
+
17
+ ### Changed
18
+
19
+ - **Docs: install from the public registries** — `README.md`, `docs/USER_GUIDE.md` and `java/README.md` now install `@nouhouari/conductor-e2e` / `@nouhouari/conductor-mcp` from npmjs.org and `io.github.nouhouari.conductor:conductor-core` from Maven Central (Maven + Gradle snippets), with no `.npmrc`, `settings.xml` or token. The user guide's install command, imports and hooks `require.resolve` path use the scoped `@nouhouari/conductor-e2e` name, and the README flags the unscoped `conductor-e2e` / `conductor-mcp` packages as the frozen pre-rename names.
20
+ - **Docs: MCP configuration and upgrading** — `README.md` gained "Configure the MCP server" and "Upgrading" sections (TypeScript, Java groupId move, MCP). MCP snippets in `README.md`, `mcp/README.md` and `docs/USER_GUIDE.md` use `@nouhouari/conductor-mcp@latest`, and `mcp/README.md` explains version pinning, the frozen unscoped package and the legacy `.npmrc` mapping.
21
+
22
+ ## [conductor-e2e 0.1.8] — 2026-10-02
23
+
24
+ ### Fixed
25
+
26
+ - **npmjs.org publish actually targets npmjs.org** — the second `setup-node` step in `release.yml` and `release-mcp.yml` now sets `scope: '@nouhouari'`. Without it, the `@nouhouari:registry=https://npm.pkg.github.com/` line written by the GitHub Packages step survived and overrode `--registry`, so the "Publish to npmjs.org" step re-published to GitHub Packages with the npm token and failed with E401. `conductor-e2e` 0.1.7 and `conductor-mcp` 0.2.1/0.2.2 therefore never reached npmjs.org; 0.1.8 and 0.2.3 are the first versions there.
27
+
28
+ ## [conductor-java 0.2.0] — 2026-10-02
29
+
30
+ ### Changed
31
+
32
+ - **Published to Maven Central under a new groupId** — the Java port is now `io.github.nouhouari.conductor:conductor-core` (was `com.nouhouari.conductor`, GitHub Packages only). Consumers no longer need a `<repositories>` entry or a `read:packages` token. Java package names (`com.nouhouari.conductor.*`) are unchanged.
33
+ - **`release-java.yml` publishes to Maven Central** — on `java-v*` tags it verifies the tag matches `java/pom.xml` (and refuses `-SNAPSHOT`), builds and tests `conductor-core`, attaches its jars to a GitHub Release, and runs `mvn -P release deploy`. Needs the `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD`, `MAVEN_GPG_PRIVATE_KEY` and `MAVEN_GPG_PASSPHRASE` repository secrets.
34
+ - `java/pom.xml` gained the metadata Central requires (url, license, developers, scm) and a `release` profile: sources + javadoc jars, GPG signing, and `central-publishing-maven-plugin`. The GitHub Packages `<distributionManagement>` was removed.
35
+
36
+ ### Added
37
+
38
+ - **CI coverage for the Java port** — `ci.yml` gained a `java` job (temurin JDK 21) that builds and unit-tests `conductor-core`, compiles `conductor-example`'s step definitions and page objects, and runs a Cucumber JVM `--dry-run` over `example/features` with the real glue so undefined/ambiguous steps fail the build. The example's suites need real browsers/devices and are never executed on a hosted runner.
39
+ - `java/README.md` gained a "CI & Releasing" section.
40
+
41
+ ## [conductor-mcp 0.2.3] — 2026-10-02
42
+
43
+ ### Changed
44
+
45
+ - `init_project` pins `@nouhouari/conductor-e2e` `^0.1.8`, the first version published to npmjs.org.
46
+ - `bin` path normalised to `dist/cli.js` (npm was auto-correcting `./dist/cli.js` on publish).
47
+
48
+ - **Java projects resolve `conductor-core` from Maven Central** — `init_project` with `language: "java"` now generates a `pom.xml` depending on `io.github.nouhouari.conductor:conductor-core:0.2.0`, with no GitHub Packages `<repositories>` block or `~/.m2/settings.xml` token step.
49
+ - **Docs: npm install** — `mcp/README.md` now installs `@nouhouari/conductor-mcp` from the public npm registry; the GitHub Packages `.npmrc` / `read:packages` setup is no longer required.
50
+
51
+ ## [conductor-e2e 0.1.7] — 2026-10-01
52
+
53
+ ### Changed
54
+
55
+ - **Release workflow publishes to npmjs.org** — `release.yml` now publishes `@nouhouari/conductor-e2e` to the public npm registry (via the `NPM_TOKEN` repository secret) in addition to GitHub Packages.
56
+ - **Idempotent release publishes** — publish steps in `release.yml` and `release-mcp.yml` skip versions already present on the target registry, so a partially failed release can be re-run.
57
+
58
+ ## [conductor-mcp 0.2.2] — 2026-10-01
59
+
60
+ ### Changed
61
+
62
+ - Re-release to publish `@nouhouari/conductor-mcp` to npmjs.org with the idempotent release workflow.
63
+
64
+ ## [conductor-mcp 0.2.1] — 2026-09-04
65
+
66
+ ### Changed
67
+
68
+ - **Release workflow publishes to npmjs.org** — `release-mcp.yml` now publishes `@nouhouari/conductor-mcp` to the public npm registry (via the `NPM_TOKEN` repository secret) in addition to GitHub Packages, so `npx @nouhouari/conductor-mcp` works without a GitHub Packages `.npmrc`.
69
+
70
+ - **Docs: GitHub Copilot CLI wire-up for `conductor-mcp`** — `mcp/README.md` and `docs/USER_GUIDE.md` now document the `~/.copilot/mcp-config.json` entry (including the Copilot-specific `"type": "stdio"` and `"tools"` allow-list) alongside the existing Claude Code / Cursor / Continue instructions. The user guide's setup snippet was also corrected to the published package name `@nouhouari/conductor-mcp` and now notes the GitHub Packages `.npmrc` requirement and Java target-project support.
71
+
72
+ ## [conductor-java 0.1.0] — 2026-07-30
73
+
74
+ First release of the Java port. Published to GitHub Packages as `com.nouhouari.conductor:conductor-core`.
75
+
76
+ ### Added
77
+
78
+ - **`conductor-core`** — Java/Cucumber-JVM/Playwright port of the framework, developed alongside the TypeScript implementation and driving the *same* Gherkin feature files under `example/features`.
79
+ - `ConductorWorld` with lazily-instantiated drivers, shared test data, and picocontainer-based injection into step definitions.
80
+ - Drivers: `WebDriver` (Playwright), `ApiDriver` (Playwright `APIRequestContext`), `MaestroDriver` (Maestro CLI), `JavaFxDriver` (fxagent HTTP API), `FlutterDesktopDriver` (Dart VM service), and the abstract `DatabaseDriver`.
81
+ - Tag-driven Cucumber-JVM hooks mirroring the TS `@web` / `@mobile` / `@flutter-desktop` / `@database` lifecycle, plus `BasePage`.
82
+ - YAML `ConfigLoader` with the TS precedence order (`config/default.yml` → `config/local-overrides.yml` → `config/${TEST_ENV}.yml` → environment variables) and the same env-var names.
83
+ - **`conductor-example`** (not published) — ported step definitions and page objects, plus one JUnit 5 `@Suite` class per platform tag, the Java equivalent of `example/cucumber.js`'s named profiles.
84
+
85
+ ### Notes
86
+
87
+ - Validated end-to-end against the real apps, no mocks: `ApiSuiteTest` 8/8, `WebSuiteTest` 9/9, `DesktopSuiteTest` 8/8, `FlutterDesktopSuiteTest` 5/5, `MobileSuiteTest` 8/8, `CrossPlatformSuiteTest` 10/10.
88
+ - `JavaFxDriver` targets `fxagent.jar` v1.0.0 (package `com.hin.fxagent`); waits are polled client-side via `POST /api/v1/elements/query` because the agent's `/elements/wait` cannot express `hidden`/absent states.
89
+ - The `RemoteScenarioFetcher` / `conductor-fetch-features` CLI equivalent is intentionally not ported.
90
+
91
+ ## [conductor-mcp 0.2.0] — 2026-07-30
92
+
93
+ ### Added
94
+
95
+ - **Java project support** — every project-aware tool now detects whether the working directory belongs to a TypeScript project (`cucumber.js`) or a Java/Maven project (`pom.xml` + Cucumber glue under `src/test/java`) and adapts accordingly. When both are found, the closest project to the working directory wins.
96
+ - `list_steps` parses `@Given` / `@When` / `@Then` annotations in `.java` sources.
97
+ - `list_page_objects` parses Java page-object classes and their public methods.
98
+ - `scaffold_step_def` / `scaffold_page_object` emit Java sources into the resolved glue and page packages.
99
+ - `dry_run_scenario` runs the Cucumber JVM CLI (`mvn test-compile` → `dependency:build-classpath` → `io.cucumber.core.cli.Main --dry-run`) and reports undefined/ambiguous steps. The CLI is used instead of `mvn test` because JUnit Platform suite `@ConfigurationParameter` values override `-D` system properties.
100
+ - `get_conductor_api` accepts `language: "java"` and returns the Java API surface for all 8 surfaces.
101
+ - **`init_project` accepts `language: "java"`** — bootstraps a Maven project (pom, JUnit Platform suites, `conductor.yml`, sample feature/step-def/page object, README, `.gitignore`) with configurable `groupId`, `artifactId` and `basePackage`. The `flutter-desktop` platform is now selectable.
102
+
103
+ ### Fixed
104
+
105
+ - **`wait_for_desktop_element` with `state: "hidden"` (or `disabled`) always errored** — it called the fxagent `POST /api/v1/elements/wait` endpoint, which returns HTTP 500 when nothing matches the selector, making "wait for this to disappear" impossible. The tool now polls `POST /api/v1/elements/query` client-side and reports the last-seen element on timeout. Supported states: `visible`, `hidden`, `enabled`, `disabled`, `exists`.
106
+
107
+ ## [0.1.6] — 2026-06-21
108
+
109
+ ### Added
110
+
111
+ - **Remote scenario sourcing** — opt-in mode to run Cucumber scenarios fetched from the requ scenario API (`GET /api/scenarios`) instead of the local `features/` folder. The local filesystem flow is unchanged and remains the default.
112
+ - `fetchScenarios()` / `reconstructFeatureFiles()` (exported from `src/scenarios/RemoteScenarioFetcher.ts`) — fetch scenarios with server-side filters (`project`, `story`, `requirement`, `phase`, `mode`, `tags`, `feature`, `q`, `valid`) and reconstruct them into `.feature` files grouped by feature, recovering feature-level tags, inserting `Background:`, and preserving `Examples:`.
113
+ - `conductor-fetch-features` bin (`src/scenarios/cli.ts`) — prefetch step that writes the reconstructed features to disk.
114
+ - New optional `remoteScenarios` config block with `REMOTE_SCENARIOS_*` env-var overrides.
115
+ - Example: new `remote` cucumber profile and `fetch:remote` / `test:remote` / `test:remote:dry` scripts.
116
+ - **New exported types**: `RemoteScenariosConfig`, `RemoteScenariosFilters`, `RemoteScenarioSummary`, `ReconstructResult`
117
+
118
+ ## [conductor-mcp 0.1.6] — 2026-06-21
119
+
120
+ ### Added
121
+
122
+ - **`init_project` scaffolds the remote scenario mode (opt-in)** — newly bootstrapped projects now include the `remote` cucumber profile, the `fetch:remote` / `test:remote` / `test:remote:dry` scripts, a commented `REMOTE_SCENARIOS_*` section in `.env.example`, `.remote-features/` in `.gitignore`, and a "Remote Scenarios" section in the README. Local feature files remain the default; the remote profile is inert until `npm run test:remote` is run. No new init parameter or prompt.
123
+ - Bootstrapped projects now depend on `@nouhouari/conductor-e2e@^0.1.6` (provides the `conductor-fetch-features` bin used by `fetch:remote`).
124
+
125
+ ## [0.1.5] — 2026-06-19
126
+
127
+ ### Added
128
+
129
+ - **`FlutterDesktopDriver`** — 9 new methods expanding the gesture and query API:
130
+ - `doubleTap(finder, timeoutMs?)` — double-tap gesture (`double_tap` command)
131
+ - `longPress(finder, timeoutMs?)` — long-press gesture (`long_press` command)
132
+ - `scroll(finder, dx, dy, durationMs?, frequency?, timeoutMs?)` — scroll a `Scrollable` widget by pixel offset
133
+ - `scrollIntoView(finder, alignment?, timeoutMs?)` — scroll until a widget is visible; `alignment` 0.0 = top, 0.5 = center, 1.0 = bottom
134
+ - `clearText(finder, timeoutMs?)` — clear a text field (enables text-entry emulation, taps to focus, sends empty string)
135
+ - `isVisible(finder, timeoutMs?)` → `boolean` — non-throwing visibility probe; returns `true`/`false` instead of throwing (default probe timeout 500 ms)
136
+ - `getOffset(finder, offsetType?, timeoutMs?)` → `{ dx, dy }` — get widget screen coordinates (`get_offset` command); `offsetType`: `'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'center'`
137
+ - `waitForCondition(condition, timeoutMs?)` — wait for the app to reach a quiescent state, the Flutter equivalent of `page.waitForLoadState()`; conditions: `'NoPendingFrames'`, `'NoTransientCallbacks'`, `'FirstFrameRasterized'`
138
+ - `setFrameSync(enabled, timeoutMs?)` — pause/resume Flutter's frame-sync during heavy animations, mirrors disabling CSS transitions in web tests
139
+ - **`FlutterDesktopDriver.connect(vmServiceUrl, timeoutMs?)`** — connect to an already-running Flutter app via its Dart VM service URL, without spawning a process. Enables use on Android (after `adb forward`), iOS (after `iproxy`), and Windows/Linux desktop. Accepts both HTTP (`http://localhost:PORT/TOKEN/`) and WebSocket (`ws://localhost:PORT/TOKEN/ws`) form. `isLaunched` now returns `true` after `connect()` as well as `launch()`.
140
+ - **New exported types**: `OffsetType`, `Offset`, `WaitCondition`
141
+ - **Example step definitions** for all new `FlutterDesktopDriver` methods
142
+
143
+ ## [conductor-mcp 0.1.4] — 2026-06-19
144
+
145
+ ### Added
146
+
147
+ - **`@nouhouari/conductor-mcp`**: Added `flutter` API surface to the built-in reference (`get_api_reference`, `list_steps` surface filter). Documents the full `FlutterDesktopDriver` public API — lifecycle (`launch`, `connect`, `close`), finders, gesture/interaction methods (`tap`, `doubleTap`, `longPress`, `enterText`, `clearText`, `scroll`, `scrollIntoView`), query methods (`getText`, `getOffset`, `isVisible`), wait methods (`waitFor`, `waitForAbsent`, `waitForCondition`), and advanced utilities (`requestData`, `setFrameSync`, `takeScreenshot`). Also documents the `@flutter-desktop` tag hook and `FlutterDesktopConfig`. Updated the `world` surface to include the `flutterDesktop` and `isFlutterDesktopLaunched` getters.
148
+
149
+ ## [conductor-mcp 0.1.2] — 2026-06-19
150
+
151
+ ### Changed
152
+
153
+ - **`@nouhouari/conductor-mcp`**: Renamed package from `conductor-mcp` to `@nouhouari/conductor-mcp` and switched registry from npm to GitHub Packages. Consumers should update their install command to `npm install @nouhouari/conductor-mcp` and configure `@nouhouari:registry=https://npm.pkg.github.com` in their `.npmrc`.
154
+
155
+ ## [0.1.3] — 2026-06-19
156
+
157
+ ### Changed
158
+
159
+ - **`conductor-e2e`**: Renamed package to `@nouhouari/conductor-e2e` and switched registry from npm to GitHub Packages. Update installs to `npm install @nouhouari/conductor-e2e` with `@nouhouari:registry=https://npm.pkg.github.com` in `.npmrc`.
160
+
161
+ ### Added
162
+
163
+ - **`FlutterDesktopDriver`** — new TypeScript driver that launches a Flutter macOS `.app` binary, connects to its Dart VM service via WebSocket, and issues `ext.flutter.driver` JSON-RPC commands. Public API: `launch()`, `close()`, `tap(finder)`, `enterText(finder, text)`, `getText(finder)`, `waitFor(finder)`, `waitForAbsent(finder)`, `requestData(message)`, `takeScreenshot(name)`.
164
+ - **`requestData()` action dispatch pattern** — primary mechanism for invoking app-side actions (toggle, edit, delete, etc.) via `enableFlutterDriverExtension`. Bypasses `hitTestable()` which never resolves for widgets inside a `ListView` on macOS desktop.
165
+ - **`@flutter-desktop` tag** — new tag-driven hook that logs driver availability before the scenario and takes a failure screenshot + closes the driver after. Lives in `src/hooks/flutterDesktop.hooks.ts`.
166
+ - **`FlutterDesktopConfig`** — new config key controlling the macOS `.app` binary path, Dart VM service port, and screenshot output directory.
167
+ - **`apps/mobile/lib/main_test.dart`** — Flutter test entry point that wires `enableFlutterDriverExtension` to the action registry via JSON dispatch.
168
+ - **`apps/mobile/lib/driver_actions.dart`** — app-side action registry for `toggleTodo`, `editTodoTitle`, `deleteTodo`, `setDialogText`, `refresh`, and `waitUntilLoaded`.
169
+ - **macOS `DebugProfile.entitlements`** — adds `network.client` entitlement so the sandboxed profile build can reach the local API server.
170
+ - **Flutter Desktop example scenarios** — feature file, step definitions, cucumber profile (`flutter-desktop`), and `npm run test:flutter-desktop` script covering home screen, create, toggle, edit, and delete (5 scenarios / 24 steps).
171
+ - **`flutter:build:macos` script** — builds the Flutter macOS app in profile mode with `main_test.dart` as the entry point and injects `API_BASE_URL` and `DISABLE_SWIPE_GESTURES` dart-defines.
172
+
173
+ ### Fixed
174
+
175
+ - macOS sandbox blocked outgoing HTTP from the profile build (missing `network.client` entitlement).
176
+ - Race condition between `initState _loadTodos()` and test setup — resolved via `waitUntilLoaded` requestData action.
177
+ - Flutter driver touch events do not fire `onPressed` on macOS desktop — bypassed via `requestData`.
178
+ - `TestTextInput` mock `_client` is null on macOS — text is now set directly on the controller.
179
+
180
+ ## [conductor-mcp 0.1.1] — 2026-05-29
181
+
182
+ ### Fixed
183
+
184
+ - **conductor-mcp**: Quote handling in `scaffold_page_object`, `scaffold_step_def`, and `scaffold_maestro_flow`. Locator selectors like `role=textbox[name='Title']`, step patterns containing apostrophes (`I don't see {string}`), and Maestro values containing `"` were dropped verbatim into single-quoted JS literals or YAML scalars, producing broken source. All three sites now use `JSON.stringify`, which selects the quote style and escapes special characters. Fixes #1.
185
+
186
+ ### Added
187
+
188
+ - **conductor-mcp@0.1.0** — Model Context Protocol (stdio) server for AI-assisted E2E test authoring. Exposes 12 tools across discovery (list steps, page objects, flows, features), scaffolding (bootstrap new projects, create features/steps/pages/flows), and validation (dry-run scenarios). Lets Claude Code, Cursor, and Continue users bootstrap a Conductor project or add tests without leaving their editor. Versioned independently on `mcp-v*` tag prefix.
189
+ - **conductor-mcp**: Discovery/scaffolding/validation tools now accept an optional `projectPath` argument so the AI can point the server at a specific Conductor project. When omitted, `resolveProjectContext` now also walks **down** one level through common subdirs (`tests/`, `e2e/`, `e2e-conductor/`, `qa/`, …) — makes the server usable in monorepos where the test project lives below the workspace root.
190
+ - **conductor-mcp**: Bootstrap template `cucumber.js` now includes the `summary` formatter alongside `progress-bar`, so a failing run prints the actual error instead of three opaque lines.
191
+ - **conductor-mcp**: Scaffolded `api.steps.ts` now ships a working `POST` example demonstrating the correct `ApiDriver.post(url, body, options?)` signature — preempts users reaching for the Playwright `{ data: ... }` shape.
192
+ - **conductor-mcp**: `scaffold_step_def` now derives parameter identifiers correctly via a three-tier strategy: (1) explicit `paramNames` if supplied, (2) **TypeScript AST walk** of the body — parses with `ts.createSourceFile` and classifies each `Identifier` by its parent node, so property keys in object literals (`{ email: ... }`), member-access names (`account.email`), declarations (`const`/`let`/`var`/parameters/destructuring), labels, and type-position references are all correctly excluded; shorthand property assignments (`{ password }`) are recognized as references; (3) generic `value`/`value2`/`count` as last resort. The earlier regex heuristic mistook property keys for references and dropped identifiers inside template-literal interpolations — both blockers reported in dogfood. `typescript` is now a runtime dep of `conductor-mcp` (added to the published package).
193
+ - **conductor-mcp**: `scaffold_page_object` accepts per-method `body` (in addition to `signature`), so users can ship working method implementations instead of `// TODO` stubs. The renderer no longer adds `async` to methods with non-Promise return types like `Locator`, fixing TS2326 errors on common page-object patterns.
194
+ - **conductor-mcp**: Scaffolded `tsconfig.json` now includes `"DOM"` in `lib`, so step definitions reaching `localStorage` / `Storage` / `Window` via `page.evaluate(...)` type-check correctly.
195
+ - **conductor-mcp**: Scaffolded `cucumber.js` now loads `support/**/*.ts` between the framework hooks and step definitions, and `init_project` scaffolds a `support/timeout.ts` that bumps Cucumber's default 5-second step timeout to 30 seconds — fixes spurious timeouts on real browser/app/HTTP steps.
196
+ - **conductor-mcp**: `init_project.includeSamples` now defaults to `false`. The previous `true` default left placeholder `example.feature` files in every project, surfacing as undefined-scenario noise in `npm test`. Real scenarios should come from `scaffold_feature` / `scaffold_step_def`. Set `includeSamples: true` explicitly only when the user wants a runnable demo.
197
+ - **conductor-mcp**: `init_project` accepts an optional `webBaseUrl` so the user's target URL ends up in `.env.example` directly. The tool description teaches the AI to ask the user for this during setup; if no URL is provided, the project uses `http://localhost:3000` as a safe default.
198
+ - **conductor-mcp**: New tool `remove_samples` — deletes the bootstrap placeholder files (`example.feature`, sample step-defs, `ExamplePage.ts`, sample Maestro flows). Only deletes files whose content is byte-equivalent to the original sample, so user edits are preserved (pass `force: true` to override). Optional `platforms` arg restricts the scope. Empty directories left behind are removed too. Total tool count is now **13**.
199
+
200
+ ## [0.1.2] — 2026-05-29
201
+
202
+ ### Changed
203
+
204
+ - **`conductor-e2e`**: `loadConfig()` now calls `import 'dotenv/config'` at module load, so a project's `.env` file is read automatically. Previously the scaffold wrote `.env.example` and the README referenced it, but nothing actually loaded it.
205
+ - **`conductor-e2e`**: Default `api.baseUrl` is now `http://localhost:3000` (no `/api` suffix). Step definitions write the full path including any `/api/...` prefix, avoiding the previous `/api/api/...` collision. `.env.example` updated accordingly. **Migration**: if your step defs wrote `${baseUrl}/todos` expecting `/api/` to be baked into baseUrl, prepend `/api/` to those URLs (or set `API_BASE_URL=http://localhost:3000/api` in your `.env` to keep the old behavior).
206
+
207
+ ### Added
208
+
209
+ - **`conductor-e2e`**: `dotenv` is now a runtime dependency.
210
+
211
+ ## [0.1.1] — 2026-05-29
212
+
213
+ ### Changed
214
+
215
+ - **Renamed npm package from `conductor` to `conductor-e2e`.** The name `conductor` is taken on the npm registry by an unrelated project; v0.1.0 was tagged but could not be published. v0.1.1 is the first version published to npm. Consumers should `npm install conductor-e2e` and import from `'conductor-e2e'`.
216
+
217
+ ## [0.1.0] — 2026-05-29
218
+
219
+ ### Added
220
+
221
+ - `ConductorWorld` — Cucumber `World` subclass that lazily instantiates web/api/maestro/desktop/database drivers
222
+ - `WebDriver` — Playwright browser lifecycle (chromium / firefox / webkit), failure screenshots
223
+ - `ApiDriver` — Playwright `APIRequestContext` wrapper, optional cookie sharing with web context
224
+ - `MaestroDriver` — spawns Maestro CLI for Flutter / native mobile testing; live stdout streaming via `DEBUG_MAESTRO`; auto-retry with `--reinstall-driver` on gRPC `UNAVAILABLE`; adb-based screenshots
225
+ - `JavaFxDriver` integration via `javafx-driver@^0.2.2` for desktop JavaFX app automation
226
+ - `DatabaseDriver` — abstract class; users register their own adapter via `world.setDb(adapter)`
227
+ - `BasePage` — Playwright page object base class with `navigate()`, `waitForLoad()`, `getTitle()`
228
+ - Tag-driven hooks: `@web`, `@mobile`, `@desktop`, `@database`, `@cross-platform`
229
+ - Environment config system with overlays (`default` / `dev` / `staging`) and env var overrides (`WEB_BASE_URL`, `API_BASE_URL`, `HEADLESS`, `BROWSER`, `MAESTRO_DEVICE`)
230
+ - Pino-based structured logger
231
+ - `retry()` helper with fixed/exponential backoff
232
+ - Allure reporting integration via `allure-cucumberjs`
233
+ - Example project (`example/`) demonstrating web + API + mobile + desktop + cross-platform scenarios
234
+ - Sample apps under test (`apps/mobile`, `apps/desktop`, `apps/server`)
235
+ - GitHub Actions CI (build, type-check, dry-run scenarios)
236
+ - GitHub Actions release pipeline (tag-triggered, builds, creates GitHub release, publishes to npm with provenance)
237
+ - [User Guide](docs/USER_GUIDE.md) covering project bootstrap, configuration, all platforms, Allure, CI/CD, troubleshooting
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nourreddine Houari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,197 @@
1
- # Temporary Holding Version
1
+ # Conductor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/nouhouari/conductor/actions/workflows/ci.yml/badge.svg)](https://github.com/nouhouari/conductor/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/conductor-e2e.svg?label=conductor-e2e)](https://www.npmjs.com/package/conductor-e2e)
5
+ [![npm version](https://img.shields.io/npm/v/conductor-mcp.svg?label=conductor-mcp)](https://www.npmjs.com/package/conductor-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ A multi-platform E2E test framework where **one Cucumber scenario** can drive a **web browser**, a **REST API**, a **Flutter mobile app**, a **Flutter desktop app** (macOS), a **JavaFX desktop app**, and a **database** — all from TypeScript.
9
+
10
+ ```gherkin
11
+ @cross-platform
12
+ Scenario: Todo created on web appears on the Flutter mobile app
13
+ Given I am on the todo web application
14
+ When I log in as "user@example.com" with password "secret"
15
+ And I create a todo titled "E2E Cross Platform"
16
+ Then the todo "E2E Cross Platform" appears on the web dashboard
17
+ And the API should return the todo "E2E Cross Platform" with status "open"
18
+ And the Flutter app should display "E2E Cross Platform" in the todo list
19
+ ```
20
+
21
+ ## Stack
22
+
23
+ | Concern | Technology |
24
+ |---|---|
25
+ | BDD runner | [`@cucumber/cucumber`](https://github.com/cucumber/cucumber-js) v11 |
26
+ | Web automation | [Playwright](https://playwright.dev) |
27
+ | API testing | Playwright `APIRequestContext` |
28
+ | Mobile automation | [Maestro CLI](https://maestro.mobile.dev) (Flutter / native) |
29
+ | Desktop automation (Flutter) | Dart VM service / `ext.flutter.driver` JSON-RPC (macOS) |
30
+ | Desktop automation (JavaFX) | [`javafx-driver`](https://www.npmjs.com/package/javafx-driver) |
31
+ | Database | Plugin interface (bring your own adapter) |
32
+ | Reporting | [Allure](https://allurereport.org/) (`allure-cucumberjs`) |
33
+
34
+ ## Quick Start
35
+
36
+ Conductor is published to the public registries — no `.npmrc`, `settings.xml` or tokens needed.
37
+
38
+ **TypeScript** — [`@nouhouari/conductor-e2e`](https://www.npmjs.com/package/@nouhouari/conductor-e2e) on npmjs.org:
39
+
40
+ ```bash
41
+ npm install @nouhouari/conductor-e2e
42
+ ```
43
+
44
+ **Java** — [`io.github.nouhouari.conductor:conductor-core`](https://central.sonatype.com/artifact/io.github.nouhouari.conductor/conductor-core) on Maven Central (see [java/README.md](java/README.md)):
45
+
46
+ ```xml
47
+ <dependency>
48
+ <groupId>io.github.nouhouari.conductor</groupId>
49
+ <artifactId>conductor-core</artifactId>
50
+ <version>0.2.0</version>
51
+ <scope>test</scope>
52
+ </dependency>
53
+ ```
54
+
55
+ **AI-assisted authoring** — the [`@nouhouari/conductor-mcp`](https://www.npmjs.com/package/@nouhouari/conductor-mcp) MCP server runs straight from npmjs.org with `npx -y @nouhouari/conductor-mcp` (see [mcp/README.md](mcp/README.md)).
56
+
57
+ > The unscoped npm packages [`conductor-e2e`](https://www.npmjs.com/package/conductor-e2e) (last: 0.1.2) and
58
+ > [`conductor-mcp`](https://www.npmjs.com/package/conductor-mcp) (last: 0.1.1) are the pre-rename names and are no
59
+ > longer updated — use the `@nouhouari/` scoped packages above.
60
+
61
+ ### Configure the MCP server
62
+
63
+ For Claude Code or Cursor, add `.mcp.json` to your project root:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "conductor": {
69
+ "command": "npx",
70
+ "args": ["-y", "@nouhouari/conductor-mcp@latest"]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ GitHub Copilot CLI (`~/.copilot/mcp-config.json`) additionally needs `"type": "stdio"` and `"tools": ["*"]`; Continue uses `~/.continue/config.json`. Start the client from inside your Conductor project and restart it after changing the config. See [mcp/README.md → Wire Up](mcp/README.md#wire-up) for every client, version pinning and alternatives to `npx`.
77
+
78
+ See the [**User Guide**](docs/USER_GUIDE.md) for a step-by-step walkthrough of bootstrapping a new E2E project from scratch.
79
+
80
+ ## Why Conductor?
81
+
82
+ Most E2E frameworks pick one platform. When your product lives on multiple platforms — a web dashboard, a mobile app, a REST API, a desktop client — you end up with **N parallel test suites** that can't share scenarios, page objects, or data lifecycle.
83
+
84
+ Conductor unifies them behind a single [`ConductorWorld`](src/world/ConductorWorld.ts):
85
+
86
+ ```typescript
87
+ async function (this: ConductorWorld) {
88
+ await this.web.launch(); // Playwright browser
89
+ await this.page.goto('/login'); // active page
90
+ await this.api.post('/todos', { title: ... }); // shared HTTP client
91
+ await this.maestro.runOrThrow('verify-todo'); // Flutter mobile flow
92
+ await this.flutterDesktop.tap('addButton'); // Flutter desktop (macOS)
93
+ await this.fx.locator('#save-btn').click(); // JavaFX desktop
94
+ await this.db.query('SELECT ...'); // your adapter
95
+ }
96
+ ```
97
+
98
+ Drivers are lazily instantiated. Tag-driven hooks manage their lifecycle:
99
+
100
+ | Tag | Effect |
101
+ |---|---|
102
+ | `@web` / `@cross-platform` | Launches browser, screenshots failures, closes |
103
+ | `@mobile` / `@cross-platform` | Targets the configured Maestro device |
104
+ | `@flutter-desktop` | Launches Flutter macOS app, screenshots failures, closes |
105
+ | `@desktop` / `@cross-platform` | Launches JavaFX app via agent JAR, closes |
106
+ | `@database` / `@cross-platform` | Connects DB before, disconnects after |
107
+
108
+ ## Project Structure
109
+
110
+ ```
111
+ conductor/
112
+ ├── src/ Framework library
113
+ │ ├── drivers/ WebDriver, ApiDriver, MaestroDriver, DatabaseDriver
114
+ │ ├── hooks/ Tag-driven Before/After hooks
115
+ │ ├── pages/ BasePage to extend
116
+ │ ├── world/ ConductorWorld (Cucumber World subclass)
117
+ │ └── support/ Logger, retry helpers
118
+ ├── config/ Environment configs (default/dev/staging)
119
+ ├── example/ Working multi-platform example project
120
+ ├── apps/ Sample apps under test
121
+ │ ├── mobile/ Flutter todo app (Android + macOS desktop)
122
+ │ ├── desktop/ JavaFX todo app
123
+ │ └── server/ Express server + web UI + REST API
124
+ ├── docs/ User guide, API docs
125
+ └── docker-compose.yml PostgreSQL for the example server
126
+ ```
127
+
128
+ ## Running the Example
129
+
130
+ ```bash
131
+ # 1. Start PostgreSQL + Express server
132
+ docker compose up -d
133
+ cd apps/server && npm start &
134
+
135
+ # 2. Run all scenarios except mobile (no device required)
136
+ cd example
137
+ npx cucumber-js --tags 'not @mobile' \
138
+ --require-module ts-node/register \
139
+ --require-module tsconfig-paths/register \
140
+ --require '../src/hooks/index.ts' \
141
+ --require 'step-definitions/**/*.ts' \
142
+ --format progress --format allure-cucumberjs/reporter \
143
+ features/**/*.feature
144
+
145
+ # 3. Open the Allure report
146
+ npm run report && npm run report:open
147
+ ```
148
+
149
+ For mobile, Flutter Desktop, and JavaFX desktop, see [docs/USER_GUIDE.md](docs/USER_GUIDE.md).
150
+
151
+ ### Flutter Desktop (macOS)
152
+
153
+ ```bash
154
+ # Build the test entry point (from repo root)
155
+ npm run flutter:build:macos
156
+
157
+ # Run Flutter Desktop scenarios
158
+ cd example
159
+ npm run test:flutter-desktop
160
+ ```
161
+
162
+ The build uses `lib/main_test.dart` as the entry point (which wires `enableFlutterDriverExtension`) and sets `DISABLE_SWIPE_GESTURES=true` to enable the AppBar Add button and the app-side action registry used by `requestData()`.
163
+
164
+ ## Upgrading
165
+
166
+ No code APIs changed; only package names, the Java groupId and where packages are hosted.
167
+
168
+ **TypeScript (`@nouhouari/conductor-e2e`)**
169
+
170
+ - *Coming from GitHub Packages:* delete `@nouhouari:registry=https://npm.pkg.github.com` (and its `_authToken` line) from `.npmrc`, then `npm install @nouhouari/conductor-e2e@latest`.
171
+ - *Coming from the unscoped `conductor-e2e` (≤ 0.1.2):* `npm uninstall conductor-e2e && npm install @nouhouari/conductor-e2e`, change imports to `from '@nouhouari/conductor-e2e'`, and point the hooks in `cucumber.js` at `require.resolve('@nouhouari/conductor-e2e/dist/src/hooks/index')`.
172
+
173
+ **Java (`conductor-core`)**
174
+
175
+ - Change the dependency to `io.github.nouhouari.conductor:conductor-core:0.2.0` (was `com.nouhouari.conductor`).
176
+ - Delete the `maven.pkg.github.com` `<repository>` from `pom.xml` and the matching `<server>` token from `~/.m2/settings.xml` (projects generated by `conductor-mcp` before 0.2.3 have both).
177
+ - Java package names stay `com.nouhouari.conductor.*`, so imports and the glue (`com.nouhouari.conductor.hooks`) are unchanged.
178
+
179
+ **MCP server (`@nouhouari/conductor-mcp`)**
180
+
181
+ - Use `["-y", "@nouhouari/conductor-mcp@latest"]` in your client config (not the unscoped `conductor-mcp`), remove any GitHub Packages `.npmrc` mapping, and restart the client.
182
+ - Upgrading the server only changes newly scaffolded projects; update existing ones with the TypeScript / Java steps above.
183
+
184
+ ## Documentation
185
+
186
+ - [**User Guide**](docs/USER_GUIDE.md) — bootstrap a new E2E project, write scenarios, run tests
187
+ - [**conductor-mcp**](mcp/README.md) — AI-assisted test authoring for GitHub Copilot CLI, Claude Code, Cursor, Continue
188
+ - [**Architecture**](CLAUDE.md) — framework internals (driver lifecycle, hooks, config)
189
+ - [Example project README](example/README.md) — guided tour of the working example
190
+
191
+ ## Contributing
192
+
193
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
194
+
195
+ ## License
196
+
197
+ [MIT](LICENSE) © Nourreddine Houari
@@ -0,0 +1,41 @@
1
+ import type { EnvironmentConfig } from '../types';
2
+
3
+ export const defaultConfig: EnvironmentConfig = {
4
+ name: 'default',
5
+ web: {
6
+ baseUrl: 'http://localhost:3000',
7
+ headless: true,
8
+ browserName: 'chromium'
9
+ },
10
+ api: {
11
+ baseUrl: 'http://localhost:3000',
12
+ defaultCredentials: { username: 'test@example.com', password: 'password' },
13
+ timeoutMs: 30000
14
+ },
15
+ mobile: {
16
+ flowsDir: 'flows/mobile',
17
+ timeoutMs: 120000
18
+ },
19
+ database: {
20
+ enabled: false
21
+ },
22
+ desktop: {
23
+ agentJar: require('path').resolve(__dirname, '../../apps/desktop/agent/fxagent.jar'),
24
+ defaultTimeoutMs: 10000,
25
+ screenshotDir: 'reports/screenshots'
26
+ },
27
+ flutterDesktop: {
28
+ appPath: require('path').resolve(
29
+ __dirname,
30
+ '../../apps/mobile/build/macos/Build/Products/Profile/todoapp.app/Contents/MacOS/todoapp'
31
+ ),
32
+ defaultTimeoutMs: 10000,
33
+ launchTimeoutMs: 30000,
34
+ screenshotDir: 'reports/screenshots'
35
+ },
36
+ remoteScenarios: {
37
+ baseUrl: 'http://localhost:8788/api',
38
+ outputDir: '.remote-features',
39
+ filters: {}
40
+ }
41
+ };
@@ -0,0 +1,16 @@
1
+ import type { EnvironmentConfig } from '../types';
2
+
3
+ export const devConfig: Partial<EnvironmentConfig> = {
4
+ name: 'dev',
5
+ web: {
6
+ baseUrl: 'http://localhost:3000',
7
+ headless: false,
8
+ browserName: 'chromium',
9
+ slowMo: 100
10
+ },
11
+ api: {
12
+ baseUrl: 'http://localhost:3000/api',
13
+ defaultCredentials: { username: 'dev@example.com', password: 'devpassword' },
14
+ timeoutMs: 60000
15
+ }
16
+ };
@@ -0,0 +1,15 @@
1
+ import type { EnvironmentConfig } from '../types';
2
+
3
+ export const stagingConfig: Partial<EnvironmentConfig> = {
4
+ name: 'staging',
5
+ web: {
6
+ baseUrl: 'https://staging.example.com',
7
+ headless: true,
8
+ browserName: 'chromium'
9
+ },
10
+ api: {
11
+ baseUrl: 'https://staging.example.com/api',
12
+ defaultCredentials: { username: 'staging@example.com', password: 'stagingpassword' },
13
+ timeoutMs: 45000
14
+ }
15
+ };
@@ -0,0 +1,70 @@
1
+ import 'dotenv/config';
2
+
3
+ import { defaultConfig } from './environments/default';
4
+ import type { EnvironmentConfig } from './types';
5
+
6
+ function loadConfig(): EnvironmentConfig {
7
+ const env = process.env.TEST_ENV ?? 'default';
8
+ let base = defaultConfig;
9
+
10
+ if (env !== 'default') {
11
+ try {
12
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
13
+ const envModule = require(`./environments/${env}`);
14
+ const envConfig = envModule[`${env}Config`] as EnvironmentConfig;
15
+ base = deepMerge(base, envConfig);
16
+ } catch {
17
+ // fall back to default
18
+ }
19
+ }
20
+
21
+ // env-var overrides
22
+ if (process.env.WEB_BASE_URL) base.web.baseUrl = process.env.WEB_BASE_URL;
23
+ if (process.env.API_BASE_URL) base.api.baseUrl = process.env.API_BASE_URL;
24
+ if (process.env.HEADLESS) base.web.headless = process.env.HEADLESS !== 'false';
25
+ if (process.env.BROWSER) base.web.browserName = process.env.BROWSER as EnvironmentConfig['web']['browserName'];
26
+ if (process.env.MAESTRO_DEVICE) base.mobile.deviceId = process.env.MAESTRO_DEVICE;
27
+ if (process.env.FLUTTER_DESKTOP_APP_PATH && base.flutterDesktop) {
28
+ base.flutterDesktop.appPath = process.env.FLUTTER_DESKTOP_APP_PATH;
29
+ }
30
+ if (process.env.FLUTTER_DESKTOP_VM_PORT && base.flutterDesktop) {
31
+ base.flutterDesktop.vmServicePort = Number(process.env.FLUTTER_DESKTOP_VM_PORT);
32
+ }
33
+
34
+ if (base.remoteScenarios) {
35
+ const rs = base.remoteScenarios;
36
+ if (process.env.REMOTE_SCENARIOS_URL) rs.baseUrl = process.env.REMOTE_SCENARIOS_URL;
37
+ if (process.env.REMOTE_SCENARIOS_PROJECT) rs.project = process.env.REMOTE_SCENARIOS_PROJECT;
38
+ if (process.env.REMOTE_SCENARIOS_OUTPUT_DIR) rs.outputDir = process.env.REMOTE_SCENARIOS_OUTPUT_DIR;
39
+ rs.filters = rs.filters ?? {};
40
+ if (process.env.REMOTE_SCENARIOS_STORY) rs.filters.story = process.env.REMOTE_SCENARIOS_STORY;
41
+ if (process.env.REMOTE_SCENARIOS_REQUIREMENT) rs.filters.requirement = process.env.REMOTE_SCENARIOS_REQUIREMENT;
42
+ if (process.env.REMOTE_SCENARIOS_PHASE) rs.filters.phase = process.env.REMOTE_SCENARIOS_PHASE;
43
+ if (process.env.REMOTE_SCENARIOS_MODE) {
44
+ rs.filters.mode = process.env.REMOTE_SCENARIOS_MODE as 'cumulative' | 'strict';
45
+ }
46
+ if (process.env.REMOTE_SCENARIOS_TAGS) rs.filters.tags = process.env.REMOTE_SCENARIOS_TAGS;
47
+ if (process.env.REMOTE_SCENARIOS_FEATURE) rs.filters.feature = process.env.REMOTE_SCENARIOS_FEATURE;
48
+ if (process.env.REMOTE_SCENARIOS_Q) rs.filters.q = process.env.REMOTE_SCENARIOS_Q;
49
+ if (process.env.REMOTE_SCENARIOS_VALID) rs.filters.valid = process.env.REMOTE_SCENARIOS_VALID === 'true';
50
+ }
51
+
52
+ return base;
53
+ }
54
+
55
+ function deepMerge<T>(base: T, override: Partial<T>): T {
56
+ const result = { ...base };
57
+ for (const key of Object.keys(override ?? {}) as (keyof T)[]) {
58
+ const ov = override[key];
59
+ if (ov !== null && typeof ov === 'object' && !Array.isArray(ov)) {
60
+ result[key] = deepMerge(result[key] as object, ov as object) as T[typeof key];
61
+ } else if (ov !== undefined) {
62
+ result[key] = ov as T[typeof key];
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+
68
+ export const config = loadConfig();
69
+ export { loadConfig };
70
+ export type { EnvironmentConfig };