@gnuechtel/nx-cucumber 2.0.0-beta.3 → 2.0.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 +204 -51
- package/migrations.json +10 -0
- package/package.json +6 -9
- package/src/adapters/registry.js +6 -1
- package/src/adapters/registry.js.map +1 -1
- package/src/core/cucumber-run-metadata-formatter.js +111 -0
- package/src/generators/project/files/all/cucumber.js.template +17 -5
- package/src/generators/project/files/playwright/run-e2e.js.template +112 -0
- package/src/generators/project/project.js +72 -29
- package/src/generators/project/project.js.map +1 -1
- package/src/index.d.ts +1 -4
- package/src/index.js +3 -6
- package/src/index.js.map +1 -1
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.d.ts +20 -0
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.js +222 -0
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.js.map +1 -0
- package/src/plugin/create-nodes.d.ts +14 -0
- package/src/plugin/create-nodes.js +4 -0
- package/src/plugin/create-nodes.js.map +1 -1
- package/src/core/cucumber-report-html.d.ts +0 -11
- package/src/core/cucumber-report-html.js +0 -58
- package/src/core/cucumber-report-html.js.map +0 -1
- package/src/core/web-server.d.ts +0 -29
- package/src/core/web-server.js +0 -139
- package/src/core/web-server.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Plugin to use Cucumber within Nx workspaces
|
|
|
11
11
|
- [Install Nx](#install-nx)
|
|
12
12
|
- [Install dependencies](#install-dependencies)
|
|
13
13
|
- [Generate Cucumber projects](#generate-cucumber-projects)
|
|
14
|
+
- [Choosing the --project value](#choosing-the---project-value)
|
|
14
15
|
- [Basic project generator usage](#basic-project-generator-usage)
|
|
15
16
|
- [Additional project generator options](#additional-project-generator-options)
|
|
16
17
|
- [Execute Cucumber projects](#execute-cucumber-projects)
|
|
@@ -24,9 +25,9 @@ Plugin to use Cucumber within Nx workspaces
|
|
|
24
25
|
- [Playwright world](#playwright-world)
|
|
25
26
|
- [Core world](#core-world)
|
|
26
27
|
- [Reports](#reports)
|
|
27
|
-
- [Built-in
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
28
|
+
- [Built-in formatter outputs](#built-in-formatter-outputs)
|
|
29
|
+
- [Run metadata report](#run-metadata-report)
|
|
30
|
+
- [Custom formatter configuration](#custom-formatter-configuration)
|
|
30
31
|
- [Code coverage](#code-coverage)
|
|
31
32
|
- [Example projects](#example-projects)
|
|
32
33
|
- [Main examples](#main-examples)
|
|
@@ -64,6 +65,9 @@ Version 2.x is a deliberate breaking release that simplifies the plugin architec
|
|
|
64
65
|
- **Nx >=22 only.** The 2.x line requires Nx 22 or later.
|
|
65
66
|
- **Adapter-oriented architecture.** The plugin uses an adapter contract to support multiple executable-spec frameworks. See [docs/adapters/contract.md](https://gitlab.com/gnuechtel/open-source/-/blob/main/docs/adapters/contract.md) for details.
|
|
66
67
|
- **Faster execution.** Removing the executor bootstrap, in-process Cucumber API, plugin-managed dev server lifecycle, and report post-processing from the execution path results in faster test runs.
|
|
68
|
+
- **Reporting is formatter-first.** Generated projects now rely on Cucumber-native formatters (`html`, `json`, `message`, `junit`) plus a dedicated run-metadata formatter artifact. No HTML mutation layer is used.
|
|
69
|
+
- **Legacy HTML post-processing removed.** The previously exported `createBeautifulHtmlReport` helper and `cucumber-html-reporter` integration are no longer part of 2.x.
|
|
70
|
+
- **Configuration filename scope.** In 2.x, plugin discovery/generation/migration is centered on `cucumber.js`.
|
|
67
71
|
|
|
68
72
|
## Prerequisites
|
|
69
73
|
|
|
@@ -91,11 +95,14 @@ npm i -D @gnuechtel/nx-cucumber
|
|
|
91
95
|
# register the plugin in nx.json (enables inferred tasks)
|
|
92
96
|
# add "@gnuechtel/nx-cucumber" to the "plugins" array in nx.json
|
|
93
97
|
|
|
94
|
-
#
|
|
95
|
-
npx nx
|
|
98
|
+
# inspect available Nx project ids (quick-start app id is @gnu-repo/gnu-app)
|
|
99
|
+
npx nx show projects
|
|
96
100
|
|
|
97
|
-
#
|
|
98
|
-
npx nx
|
|
101
|
+
# create a cucumber project for the quick-start app project id
|
|
102
|
+
npx nx g @gnuechtel/nx-cucumber:project --project="@gnu-repo/gnu-app" --test-runner=playwright
|
|
103
|
+
|
|
104
|
+
# run cucumber scenarios with the generated e2e project id
|
|
105
|
+
npx nx e2e "@gnu-repo/gnu-app-e2e"
|
|
99
106
|
```
|
|
100
107
|
|
|
101
108
|
## Install Nx
|
|
@@ -122,27 +129,43 @@ If we want to use the Playwright test runner, we should also install one of the
|
|
|
122
129
|
|
|
123
130
|
We recommend using `@playwright/test`, which also includes the `playwright` dependency and provides very useful [expect](https://playwright.dev/docs/test-assertions) extensions.
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- [cucumber-html-reporter](https://www.npmjs.com/package/cucumber-html-reporter) is required for the [beautiful HTML report](#beautiful-html-report).
|
|
132
|
+
No additional reporting dependency is required for the default outputs.
|
|
128
133
|
|
|
129
134
|
## Generate Cucumber projects
|
|
130
135
|
|
|
136
|
+
### Choosing the `--project` value
|
|
137
|
+
|
|
138
|
+
`--project` must be an existing Nx project id, not the workspace folder name.
|
|
139
|
+
|
|
140
|
+
Use `npx nx show projects` to list valid project ids in your workspace. For Nx 22 monorepo presets (including this quick-start), app project ids are scoped by default (for example `@gnu-repo/gnu-app`).
|
|
141
|
+
|
|
142
|
+
Some existing/legacy workspaces can still use unscoped ids, so always copy the exact id from `npx nx show projects`.
|
|
143
|
+
|
|
144
|
+
If the id is scoped, quote it in shell commands:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npx nx g @gnuechtel/nx-cucumber:project --project="@smoke-test/smoke-test"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
If `--project` does not exist, generation fails fast with an explicit error.
|
|
151
|
+
|
|
152
|
+
When `--name` is unscoped and the tested project id is scoped, the generated e2e project id inherits the same scope.
|
|
153
|
+
|
|
131
154
|
### Basic project generator usage
|
|
132
155
|
|
|
133
156
|
After the installation of the dependencies, we can create a project with Cucumber, for any kind of node application:
|
|
134
157
|
|
|
135
158
|
```bash
|
|
136
|
-
# Prerequisite: create application
|
|
159
|
+
# Prerequisite: create an application and identify its Nx project id via "npx nx show projects"
|
|
137
160
|
|
|
138
161
|
# default core test runner (generates an e2e app 'my-project-e2e')
|
|
139
|
-
npx nx g @gnuechtel/nx-cucumber:project --project=
|
|
162
|
+
npx nx g @gnuechtel/nx-cucumber:project --project="<app-project-id>"
|
|
140
163
|
|
|
141
164
|
# playwright test runner (also generates an e2e app 'my-project-e2e')
|
|
142
|
-
npx nx g @gnuechtel/nx-cucumber:project --project=
|
|
165
|
+
npx nx g @gnuechtel/nx-cucumber:project --project="<app-project-id>" --test-runner=playwright
|
|
143
166
|
|
|
144
167
|
# playwright test runner with custom e2e project name (generates an app 'my-project-foo-e2e')
|
|
145
|
-
npx nx g @gnuechtel/nx-cucumber:project --project=
|
|
168
|
+
npx nx g @gnuechtel/nx-cucumber:project --project="<app-project-id>" --name my-project-foo-e2e --test-runner=playwright
|
|
146
169
|
```
|
|
147
170
|
|
|
148
171
|
The generator creates:
|
|
@@ -155,8 +178,8 @@ The `e2e` target behavior depends on the selected test runner:
|
|
|
155
178
|
|
|
156
179
|
- **Core runner:** no explicit `e2e` target is generated. The `e2e` task is inferred by the plugin from `cucumber.js`.
|
|
157
180
|
- **Playwright runner:** explicit command targets are generated:
|
|
158
|
-
- `e2e-prepare` installs Playwright Chromium (Linux uses `--with-deps`)
|
|
159
|
-
- `e2e` runs `
|
|
181
|
+
- `e2e-prepare` installs Playwright Chromium (Linux uses `--with-deps`) and is configured with `cache: false`.
|
|
182
|
+
- `e2e` runs a generated `run-e2e.js` lifecycle script that manages the full dev server lifecycle (clear port, start server, wait for readiness, run Cucumber, stop server) using only Node.js built-ins.
|
|
160
183
|
- `e2e-ci` runs `e2e-prepare` and then `e2e`.
|
|
161
184
|
|
|
162
185
|
### Additional project generator options
|
|
@@ -223,6 +246,8 @@ module.exports = {
|
|
|
223
246
|
|
|
224
247
|
All advanced Cucumber [options](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options) can be set in the `cucumber.js` file.
|
|
225
248
|
|
|
249
|
+
> **2.x limitation:** `@gnuechtel/nx-cucumber` currently supports `cucumber.js` as the plugin configuration file for inferred-task discovery, generator output, and migration output. Other Cucumber config filenames (for example `cucumber.mjs`) are not supported by the plugin in this major version.
|
|
250
|
+
|
|
226
251
|
### World parameters
|
|
227
252
|
|
|
228
253
|
Playwright-related options that were previously set in `project.json` executor options are now configured via `worldParameters` in `cucumber.js`:
|
|
@@ -330,36 +355,26 @@ export class CustomWorld extends World {
|
|
|
330
355
|
|
|
331
356
|
## Reports
|
|
332
357
|
|
|
333
|
-
### Built-in
|
|
334
|
-
|
|
335
|
-
The generator configures the Cucumber built-in `html` formatter by default. The report is generated at `dist/e2e/<project-root>/reports/html/cucumber-test-report-builtin.html`.
|
|
358
|
+
### Built-in formatter outputs
|
|
336
359
|
|
|
337
|
-
|
|
360
|
+
Generated projects configure Cucumber-native formatter outputs by default:
|
|
338
361
|
|
|
339
|
-
|
|
362
|
+
- HTML: `dist/e2e/<project-root>/reports/html/cucumber-test-report-builtin.html`
|
|
363
|
+
- JSON: `dist/e2e/<project-root>/reports/cucumber-json/cucumber-test-report.json`
|
|
364
|
+
- NDJSON messages: `dist/e2e/<project-root>/reports/cucumber-messages/cucumber-messages.ndjson`
|
|
365
|
+
- XML (JUnit): `dist/e2e/<project-root>/reports/test-results/cucumber-test-report.xml`
|
|
340
366
|
|
|
341
|
-
|
|
342
|
-
npm i -D cucumber-html-reporter
|
|
343
|
-
```
|
|
367
|
+
### Run metadata report
|
|
344
368
|
|
|
345
|
-
|
|
369
|
+
Generated projects also include an additional metadata artifact:
|
|
346
370
|
|
|
347
|
-
|
|
348
|
-
import { createBeautifulHtmlReport } from '@gnuechtel/nx-cucumber';
|
|
371
|
+
- Metadata JSON: `dist/e2e/<project-root>/reports/metadata/cucumber-run-metadata.json`
|
|
349
372
|
|
|
350
|
-
|
|
351
|
-
jsonDir: 'dist/e2e/apps/my-project-e2e/reports/cucumber-json',
|
|
352
|
-
outputDir: 'dist/e2e/apps/my-project-e2e/reports/html',
|
|
353
|
-
reportTitle: 'My Project E2E Tests',
|
|
354
|
-
});
|
|
355
|
-
```
|
|
373
|
+
This file is written by `@gnuechtel/nx-cucumber/src/core/cucumber-run-metadata-formatter.js` and is intentionally separate from Cucumber's standard JSON/NDJSON schema. It contains run context (for example Cucumber meta info, run timestamps, selected world parameters, and environment hints like browser/baseUrl/mode).
|
|
356
374
|
|
|
357
|
-
###
|
|
375
|
+
### Custom formatter configuration
|
|
358
376
|
|
|
359
|
-
The
|
|
360
|
-
|
|
361
|
-
- JSON: `dist/e2e/<project-root>/reports/cucumber-json/cucumber-test-report.json`
|
|
362
|
-
- XML (JUnit): `dist/e2e/<project-root>/reports/test-results/cucumber-test-report.xml`
|
|
377
|
+
The plugin does not lock formatter choices. You can freely add, remove, or replace formatters in your `cucumber.js` file. The generated metadata formatter receives values via `formatOptions.nxCucumberRunMetadata.worldParameters`, while normal runtime behavior still uses `worldParameters`.
|
|
363
378
|
|
|
364
379
|
## Code coverage
|
|
365
380
|
|
|
@@ -390,6 +405,8 @@ If we have executed our nx-cucumber [end-to-end tests](#end-to-end-tests-of-this
|
|
|
390
405
|
|
|
391
406
|
We support Cucumber with this plugin, and it would be a shame if we used anything other than Cucumber for our own end to end tests. So, we [just do it](https://gitlab.com/gnuechtel/open-source/-/blob/main/apps/nx-cucumber-e2e/features/nx-cucumber.feature)!
|
|
392
407
|
|
|
408
|
+
Generation coverage includes validation for unknown `--project` ids and execution against real Nx project ids detected in the test workspace.
|
|
409
|
+
|
|
393
410
|
To run our own end-to-end tests we simply execute them by:
|
|
394
411
|
|
|
395
412
|
```bash
|
|
@@ -426,12 +443,15 @@ PWDEBUG=1 npx nx e2e my-project-e2e
|
|
|
426
443
|
|
|
427
444
|
### Watching
|
|
428
445
|
|
|
429
|
-
|
|
446
|
+
There is no dedicated watch mode in `@gnuechtel/nx-cucumber`.
|
|
447
|
+
If you want automatic re-runs on file changes, you can use Nx [workspace watching](https://nx.dev/recipes/running-tasks/workspace-watching) as an optional outer loop:
|
|
430
448
|
|
|
431
449
|
```bash
|
|
432
450
|
npx nx watch --projects=my-project-e2e -- npx nx e2e my-project-e2e
|
|
433
451
|
```
|
|
434
452
|
|
|
453
|
+
This command belongs to Nx workspace tooling, not to a plugin-specific watch implementation.
|
|
454
|
+
|
|
435
455
|
## Filter Cucumber scenarios
|
|
436
456
|
|
|
437
457
|
While development of Cucumber scenarios, we often want to execute only selected scenarios. We can achieve this by using the filter capabilities of Cucumber in the `cucumber.js` config or via CLI arguments.
|
|
@@ -546,31 +566,94 @@ Since 2.x uses framework-native execution via `npx cucumber-js`, all Cucumber CL
|
|
|
546
566
|
|
|
547
567
|
If a feature is missing, please open an issue with a minimal reproducible setup.
|
|
548
568
|
|
|
549
|
-
###
|
|
569
|
+
### Garbled terminal output with verbose Playwright logs (`pw:api`)
|
|
570
|
+
|
|
571
|
+
If log lines look overwritten or scattered (for example on macOS, Linux, or Windows terminals), there are usually two independent redraw layers:
|
|
550
572
|
|
|
551
|
-
|
|
573
|
+
1. Cucumber formatter output:
|
|
574
|
+
- `progress-bar` is a real-time updating bar (redraws terminal line).
|
|
575
|
+
- `progress` is step-by-step progress output without a live bar redraw.
|
|
576
|
+
2. Nx task output lifecycle:
|
|
577
|
+
- dynamic/TUI modes may also rewrite terminal output while tasks run.
|
|
578
|
+
- `static` prints append-only output.
|
|
579
|
+
|
|
580
|
+
References:
|
|
581
|
+
|
|
582
|
+
- Cucumber formatters (`progress` vs `progress-bar`): https://github.com/cucumber/cucumber-js/blob/main/docs/formatters.md
|
|
583
|
+
- Nx `outputStyle` modes: https://nx.dev/reference/core-api/nx/documents/show#outputstyle
|
|
584
|
+
|
|
585
|
+
nx-cucumber templates currently use `progress-bar`. If redraw artifacts are distracting with verbose `pw:api` logs, switch to `progress` (or keep only `summary`) in your local `cucumber.js`.
|
|
586
|
+
|
|
587
|
+
If you still want fully append-only Nx logs, run:
|
|
552
588
|
|
|
553
589
|
```bash
|
|
554
|
-
npx
|
|
590
|
+
npx nx e2e "<project-id>" --outputStyle=static
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
or set this in a **workspace-root** `.env`:
|
|
594
|
+
|
|
595
|
+
```env
|
|
596
|
+
NX_DEFAULT_OUTPUT_STYLE=static
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Note: nx-cucumber intentionally does not generate or modify a workspace-root `.env`, because that would change logging behavior globally for unrelated projects in the same workspace.
|
|
600
|
+
|
|
601
|
+
### Workspace sync after generating projects
|
|
602
|
+
|
|
603
|
+
On Nx 20+, running `e2e` immediately after generating a new nx-cucumber project (core or Playwright) may fail with:
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
The workspace is out of sync
|
|
607
|
+
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references...
|
|
555
608
|
```
|
|
556
609
|
|
|
557
|
-
|
|
610
|
+
This is standard Nx behaviour and is not Windows-specific. It can happen on macOS, Linux, and Windows when sync generators detect pending workspace updates.
|
|
611
|
+
|
|
612
|
+
For deterministic smoke-test runs, execute:
|
|
558
613
|
|
|
559
614
|
```bash
|
|
560
|
-
npx nx
|
|
615
|
+
npx nx sync
|
|
561
616
|
```
|
|
562
617
|
|
|
563
|
-
|
|
618
|
+
and then rerun your `npx nx e2e "<project-id>"` command.
|
|
564
619
|
|
|
565
|
-
|
|
566
|
-
- `start-server-and-test` owns start/wait/test/stop lifecycle in one process chain.
|
|
567
|
-
- `--strictPort` prevents fallback port switches by Vite that can break URL expectations.
|
|
620
|
+
To avoid this manual step in interactive environments, configure `nx.json`:
|
|
568
621
|
|
|
569
|
-
|
|
622
|
+
```json
|
|
623
|
+
{
|
|
624
|
+
"sync": { "applyChanges": true }
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
With this setting Nx automatically applies sync changes before running tasks in interactive terminals.
|
|
629
|
+
|
|
630
|
+
In non-interactive local runs (no TTY), Nx cannot prompt; if sync changes are needed, task execution stops with a workspace-out-of-sync error.
|
|
631
|
+
|
|
632
|
+
In CI on Nx 22, task-time sync prompting is skipped, so add an explicit preflight:
|
|
570
633
|
|
|
571
634
|
```bash
|
|
572
|
-
|
|
635
|
+
npx nx sync:check
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
If it reports changes are required, run `npx nx sync` and commit the resulting files.
|
|
639
|
+
|
|
640
|
+
`npx nx reset` is a recovery step for stale daemon/project-graph state (for example `Cannot find project "..."` right after generation, or a newly generated project missing from `npx nx show projects` even after `npx nx sync`). It is not the primary sync fix.
|
|
641
|
+
|
|
642
|
+
### `TS_NODE_PROJECT` and formatter file extensions
|
|
643
|
+
|
|
644
|
+
Generated `cucumber.js` files set `process.env.TS_NODE_PROJECT` explicitly. Keep this setting so `ts-node/register` resolves the correct `tsconfig.json` in monorepos.
|
|
645
|
+
|
|
646
|
+
If you add custom formatters, prefer `.js` (or precompiled output) for formatter modules referenced in `format`. Cucumber loads formatter modules via dynamic import, and pointing directly to a `.ts` formatter can fail with `ERR_UNKNOWN_FILE_EXTENSION` before TypeScript transpilation hooks are applied.
|
|
647
|
+
|
|
648
|
+
### Windows Playwright teardown noise
|
|
649
|
+
|
|
650
|
+
On Windows, after all scenarios finish, the following message may appear in the output:
|
|
651
|
+
|
|
573
652
|
```
|
|
653
|
+
ERROR: The process "XXXX" not found.
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
This is harmless. The generated `run-e2e.js` lifecycle script attempts to stop the dev server after testing. If the server has already exited on its own, `taskkill` reports this message. It does not indicate a test failure.
|
|
574
657
|
|
|
575
658
|
## Migration from 1.x
|
|
576
659
|
|
|
@@ -579,6 +662,63 @@ npm run e2e:port-regression:react-example
|
|
|
579
662
|
- Upgrade to Nx `>=22`
|
|
580
663
|
- Install `@gnuechtel/nx-cucumber@2`
|
|
581
664
|
|
|
665
|
+
### Version compatibility and upgrade order
|
|
666
|
+
|
|
667
|
+
`@gnuechtel/nx-cucumber` 1.x does not have one single Nx compatibility range (taken from published npm `peerDependencies`):
|
|
668
|
+
|
|
669
|
+
| nx-cucumber 1.x | Nx peer compatibility |
|
|
670
|
+
| --------------- | ------------------------- |
|
|
671
|
+
| `1.0.0` | `^16 \|\| ^17` |
|
|
672
|
+
| `1.0.1` | `^16 \|\| ^17 \|\| ^18` |
|
|
673
|
+
| `1.0.2` | `16 - 19` |
|
|
674
|
+
| `1.1.0` | `>=19 <23` |
|
|
675
|
+
| `1.2.0 - 1.4.1` | `>=20 <23 \|\| ^22.0.0-0` |
|
|
676
|
+
|
|
677
|
+
Recommended upgrade order for projects moving from 1.x to 2.x:
|
|
678
|
+
|
|
679
|
+
1. If your project is on `1.0.0`, `1.0.1`, or `1.0.2`, first install and run with a compatible Nx (for example Nx 17) to avoid peer-resolution conflicts.
|
|
680
|
+
2. Remove the legacy plugin package from `package.json`:
|
|
681
|
+
`npm remove @gnuechtel/nx-cucumber`
|
|
682
|
+
3. Upgrade Nx to 22:
|
|
683
|
+
`npx nx@22.6.4 migrate 22.6.4`
|
|
684
|
+
`npm install`
|
|
685
|
+
`npx nx migrate --run-migrations`
|
|
686
|
+
4. Install `@gnuechtel/nx-cucumber@2`.
|
|
687
|
+
5. Run the plugin migration (`nx migrate @gnuechtel/nx-cucumber@2` + `nx migrate --run-migrations`).
|
|
688
|
+
|
|
689
|
+
For `1.1.0+`, Nx 22 is already within the supported peer range. The temporary Nx-17 compatibility step is not required there.
|
|
690
|
+
|
|
691
|
+
### Automated migration via nx migrate
|
|
692
|
+
|
|
693
|
+
You can run the legacy executor migration as part of the standard Nx migration flow:
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
npx nx migrate @gnuechtel/nx-cucumber@2
|
|
697
|
+
npx nx migrate --run-migrations
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
What this migration does:
|
|
701
|
+
|
|
702
|
+
- Detects projects using `@gnuechtel/nx-cucumber:cucumber-api`.
|
|
703
|
+
- Adds `@gnuechtel/nx-cucumber` to `nx.json` plugins (if missing).
|
|
704
|
+
- Removes legacy executor targets (`e2e-cucumber`, and legacy `e2e` wrappers pointing to `e2e-cucumber`).
|
|
705
|
+
- Removes legacy `e2e-watch` targets that use the old `--watch-message` run-command flow.
|
|
706
|
+
- Creates a `cucumber.js` file when missing, based on detected legacy options.
|
|
707
|
+
- Writes a migration report to `tmp/nx-cucumber-migration-report.json`.
|
|
708
|
+
|
|
709
|
+
Dry-run mode:
|
|
710
|
+
|
|
711
|
+
- Set `NX_CUCUMBER_MIGRATION_DRY_RUN=true` before running migrations to print planned changes without modifying files.
|
|
712
|
+
|
|
713
|
+
### CI migration tests
|
|
714
|
+
|
|
715
|
+
The repository includes an automated migration matrix test (`@migration` scenarios) for all stable 1.x versions (`1.0.0` to `1.4.1`).
|
|
716
|
+
|
|
717
|
+
- For `1.0.0 - 1.0.2`, the test uses a compatible Nx 17 workspace first, removes the legacy plugin package, upgrades Nx to 22, then runs the 2.x migration.
|
|
718
|
+
- For `1.1.0+`, the test runs directly on Nx 22.
|
|
719
|
+
|
|
720
|
+
`npm install` failures from legacy generator internals are not treated as acceptable in these migration tests.
|
|
721
|
+
|
|
582
722
|
### Step 1: Register the plugin
|
|
583
723
|
|
|
584
724
|
Add the plugin to `nx.json`:
|
|
@@ -610,6 +750,7 @@ module.exports = {
|
|
|
610
750
|
'summary',
|
|
611
751
|
'progress-bar',
|
|
612
752
|
`json:${outputDir}/reports/cucumber-json/cucumber-test-report.json`,
|
|
753
|
+
`message:${outputDir}/reports/cucumber-messages/cucumber-messages.ndjson`,
|
|
613
754
|
`html:${outputDir}/reports/html/cucumber-test-report-builtin.html`,
|
|
614
755
|
`junit:${outputDir}/reports/test-results/cucumber-test-report.xml`,
|
|
615
756
|
],
|
|
@@ -622,7 +763,7 @@ For core (non-Playwright) projects, omit the Playwright require entries.
|
|
|
622
763
|
|
|
623
764
|
### Step 3: Simplify project.json
|
|
624
765
|
|
|
625
|
-
Remove the executor-based `e2e` and `e2e-cucumber` targets from `project.json`. The plugin infers the `e2e` target automatically from the `cucumber.js` file. Keep only non-inferred targets like `lint`.
|
|
766
|
+
Remove the executor-based `e2e` and `e2e-cucumber` targets from `project.json`. Also remove old `e2e-watch` run-commands. The plugin infers the `e2e` target automatically from the `cucumber.js` file. Keep only non-inferred targets like `lint`.
|
|
626
767
|
|
|
627
768
|
### Step 4: Move executor options to cucumber.js
|
|
628
769
|
|
|
@@ -645,3 +786,15 @@ npx nx e2e my-project-e2e
|
|
|
645
786
|
```
|
|
646
787
|
|
|
647
788
|
The inferred target runs `npx cucumber-js` with the configuration from `cucumber.js`.
|
|
789
|
+
|
|
790
|
+
### Rollback strategy
|
|
791
|
+
|
|
792
|
+
- Revert the migration commit in git if validation fails.
|
|
793
|
+
- Remove `@gnuechtel/nx-cucumber` from `nx.json` plugins if rollback to 1.x is required.
|
|
794
|
+
- Restore previous `project.json` executor targets and `cucumber.js` from version control.
|
|
795
|
+
|
|
796
|
+
### Known limitations
|
|
797
|
+
|
|
798
|
+
- Existing `cucumber.js` files are not overwritten automatically.
|
|
799
|
+
- Framework-native watch mode is not generated automatically. If you still need watch, add your own adapter-native watch command after migration.
|
|
800
|
+
- Highly customized run-command wrappers may require manual cleanup after migration.
|
package/migrations.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generators": {
|
|
3
|
+
"migrate-cucumber-api-to-inferred-tasks": {
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"description": "Migrate legacy cucumber-api executor projects to adapter-based inferred tasks",
|
|
6
|
+
"implementation": "./src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks",
|
|
7
|
+
"cli": "nx"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gnuechtel/nx-cucumber",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Plugin to use Cucumber within Nx workspaces",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"@nx/devkit": ">=22 <23",
|
|
13
13
|
"minimatch": "^10.0.0",
|
|
14
14
|
"tslib": "^2.3.0",
|
|
15
|
-
"@gnuechtel/shared-coverage": "2.0.0
|
|
16
|
-
"@gnuechtel/shared-util": "2.0.0
|
|
15
|
+
"@gnuechtel/shared-coverage": "2.0.0",
|
|
16
|
+
"@gnuechtel/shared-util": "2.0.0"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"@cucumber/cucumber": ">=9 <13",
|
|
@@ -22,17 +22,14 @@
|
|
|
22
22
|
"playwright-core": ">=1.38.0-0 <2",
|
|
23
23
|
"playwright": ">=1.38.0-0 <2",
|
|
24
24
|
"@playwright/test": ">=1.38.0-0 <2",
|
|
25
|
-
"cucumber-html-reporter": "^7.1.0",
|
|
26
25
|
"nx": ">=22 <23"
|
|
27
26
|
},
|
|
28
|
-
"peerDependenciesMeta": {
|
|
29
|
-
"cucumber-html-reporter": {
|
|
30
|
-
"optional": true
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
27
|
"type": "commonjs",
|
|
34
28
|
"main": "./src/index.js",
|
|
35
29
|
"generators": "./generators.json",
|
|
30
|
+
"nx-migrations": {
|
|
31
|
+
"migrations": "./migrations.json"
|
|
32
|
+
},
|
|
36
33
|
"executors": "./executors.json",
|
|
37
34
|
"typings": "./src/index.d.ts",
|
|
38
35
|
"types": "./src/index.d.ts"
|
package/src/adapters/registry.js
CHANGED
|
@@ -7,11 +7,16 @@ const minimatch_1 = require("minimatch");
|
|
|
7
7
|
const path_1 = require("path");
|
|
8
8
|
const cucumberJsAdapter = {
|
|
9
9
|
name: 'cucumber-js',
|
|
10
|
-
configFilePattern: 'cucumber.
|
|
10
|
+
configFilePattern: 'cucumber.js',
|
|
11
11
|
technologies: ['@cucumber/cucumber'],
|
|
12
12
|
targets: {
|
|
13
13
|
e2e: {
|
|
14
14
|
command: 'npx cucumber-js',
|
|
15
|
+
defaultOptions: {
|
|
16
|
+
env: {
|
|
17
|
+
TS_NODE_PROJECT: '',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
15
20
|
dependsOn: ['^build'],
|
|
16
21
|
},
|
|
17
22
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/adapters/registry.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/adapters/registry.ts"],"names":[],"mappings":";;AAwBA,gDAEC;AAMD,4DAMC;AAOD,gDAKC;AAlDD,yCAAsC;AACtC,+BAAgC;AAGhC,MAAM,iBAAiB,GAAsB;IAC3C,IAAI,EAAE,aAAa;IACnB,iBAAiB,EAAE,aAAa;IAChC,YAAY,EAAE,CAAC,oBAAoB,CAAC;IACpC,OAAO,EAAE;QACP,GAAG,EAAE;YACH,OAAO,EAAE,iBAAiB;YAC1B,cAAc,EAAE;gBACd,GAAG,EAAE;oBACH,eAAe,EAAE,EAAE;iBACpB;aACF;YACD,SAAS,EAAE,CAAC,QAAQ,CAAC;SACtB;KACF;CACF,CAAC;AAEF;;GAEG;AACH,SAAgB,kBAAkB;IAChC,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,SAAgB,wBAAwB,CACtC,UAAkB,EAClB,QAA6B;IAE7B,MAAM,QAAQ,GAAG,IAAA,eAAQ,EAAC,UAAU,CAAC,CAAC;IACtC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAA,qBAAS,EAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,QAA6B;IAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,OAAO,QAAQ,GAAG,CAAC;AAC5B,CAAC","sourcesContent":["import { minimatch } from 'minimatch';\nimport { basename } from 'path';\nimport { AdapterDefinition } from './contract';\n\nconst cucumberJsAdapter: AdapterDefinition = {\n name: 'cucumber-js',\n configFilePattern: 'cucumber.js',\n technologies: ['@cucumber/cucumber'],\n targets: {\n e2e: {\n command: 'npx cucumber-js',\n defaultOptions: {\n env: {\n TS_NODE_PROJECT: '',\n },\n },\n dependsOn: ['^build'],\n },\n },\n};\n\n/**\n * Returns all built-in adapter definitions.\n */\nexport function getBuiltinAdapters(): AdapterDefinition[] {\n return [cucumberJsAdapter];\n}\n\n/**\n * Finds the adapter whose configFilePattern matches the given config file path.\n * Returns undefined if no adapter matches.\n */\nexport function findAdapterForConfigFile(\n configFile: string,\n adapters: AdapterDefinition[]\n): AdapterDefinition | undefined {\n const fileName = basename(configFile);\n return adapters.find((adapter) => minimatch(fileName, adapter.configFilePattern));\n}\n\n/**\n * Builds a combined glob pattern from all registered adapter config file patterns.\n * Used by createNodesV2 to discover adapter-managed projects in one pass.\n * Returns undefined if no adapters are registered.\n */\nexport function buildDiscoveryGlob(adapters: AdapterDefinition[]): string | undefined {\n if (adapters.length === 0) return undefined;\n if (adapters.length === 1) return `**/${adapters[0].configFilePattern}`;\n const patterns = adapters.map((a) => a.configFilePattern).join(',');\n return `**/{${patterns}}`;\n}\n"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Formatter } = require('@cucumber/cucumber');
|
|
4
|
+
|
|
5
|
+
class CucumberRunMetadataFormatter extends Formatter {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(options);
|
|
8
|
+
const parsedArgvOptions = options.parsedArgvOptions || {};
|
|
9
|
+
const metadataOptions = readObject(parsedArgvOptions.nxCucumberRunMetadata);
|
|
10
|
+
this.worldParameters =
|
|
11
|
+
readWorldParameters(metadataOptions?.worldParameters) ?? readWorldParameters(parsedArgvOptions.worldParameters);
|
|
12
|
+
this.meta = undefined;
|
|
13
|
+
this.startedAt = undefined;
|
|
14
|
+
this.finishedAt = undefined;
|
|
15
|
+
this.emitted = false;
|
|
16
|
+
|
|
17
|
+
options.eventBroadcaster.on('envelope', (envelope) => {
|
|
18
|
+
if (envelope.meta && !this.meta) {
|
|
19
|
+
this.meta = envelope.meta;
|
|
20
|
+
}
|
|
21
|
+
if (envelope.testRunStarted && envelope.testRunStarted.timestamp) {
|
|
22
|
+
this.startedAt = timestampToIso(envelope.testRunStarted.timestamp);
|
|
23
|
+
}
|
|
24
|
+
if (envelope.testRunFinished) {
|
|
25
|
+
this.finishedAt = timestampToIso(envelope.testRunFinished.timestamp);
|
|
26
|
+
this.emitMetadata();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async finished() {
|
|
32
|
+
this.emitMetadata();
|
|
33
|
+
await super.finished();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
emitMetadata() {
|
|
37
|
+
if (this.emitted) return;
|
|
38
|
+
this.log(
|
|
39
|
+
`${JSON.stringify(buildRunMetadata(this.meta, this.startedAt, this.finishedAt, this.worldParameters), null, 2)}\n`
|
|
40
|
+
);
|
|
41
|
+
this.emitted = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildRunMetadata(meta, startedAt, finishedAt, worldParameters) {
|
|
46
|
+
const environment = resolveEnvironmentMetadata(worldParameters);
|
|
47
|
+
const metadata = {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
generatedAt: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (meta) metadata.meta = meta;
|
|
53
|
+
if (startedAt || finishedAt) metadata.testRun = { startedAt, finishedAt };
|
|
54
|
+
if (worldParameters && Object.keys(worldParameters).length > 0) metadata.worldParameters = worldParameters;
|
|
55
|
+
if (environment && Object.keys(environment).length > 0) metadata.environment = environment;
|
|
56
|
+
return metadata;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveEnvironmentMetadata(worldParameters) {
|
|
60
|
+
if (!worldParameters) return undefined;
|
|
61
|
+
const browser = stringValue(worldParameters.browser);
|
|
62
|
+
const baseUrl = stringValue(worldParameters.baseUrl);
|
|
63
|
+
const headed = worldParameters.headed;
|
|
64
|
+
const mode = typeof headed === 'boolean' ? (headed ? 'headed' : 'headless') : undefined;
|
|
65
|
+
const environment = {};
|
|
66
|
+
if (browser) environment.browser = browser;
|
|
67
|
+
if (baseUrl) environment.baseUrl = baseUrl;
|
|
68
|
+
if (mode) environment.mode = mode;
|
|
69
|
+
return Object.keys(environment).length > 0 ? environment : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readWorldParameters(value) {
|
|
73
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readObject(value) {
|
|
78
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function stringValue(value) {
|
|
83
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function timestampToIso(timestamp) {
|
|
87
|
+
if (!timestamp) return undefined;
|
|
88
|
+
const seconds = numberValue(timestamp.seconds);
|
|
89
|
+
if (seconds === undefined) return undefined;
|
|
90
|
+
const nanos = numberValue(timestamp.nanos) || 0;
|
|
91
|
+
return new Date(seconds * 1000 + nanos / 1000000).toISOString();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function numberValue(value) {
|
|
95
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;
|
|
96
|
+
if (typeof value === 'bigint') return Number(value);
|
|
97
|
+
if (typeof value === 'string') {
|
|
98
|
+
const parsed = Number(value);
|
|
99
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
102
|
+
if (typeof value.toNumber === 'function') {
|
|
103
|
+
const result = value.toNumber();
|
|
104
|
+
return Number.isFinite(result) ? result : undefined;
|
|
105
|
+
}
|
|
106
|
+
if (typeof value.low === 'number') return Number.isFinite(value.low) ? value.low : undefined;
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = CucumberRunMetadataFormatter;
|
|
111
|
+
module.exports.buildRunMetadata = buildRunMetadata;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
process.env.TS_NODE_PROJECT = '<%= projectRoot %>/tsconfig.json';
|
|
2
2
|
const outputDir = 'dist/e2e/<%= projectRoot %>';
|
|
3
|
+
const { pathToFileURL } = require('url');
|
|
4
|
+
const runMetadataFormatter = pathToFileURL(
|
|
5
|
+
require.resolve('@gnuechtel/nx-cucumber/src/core/cucumber-run-metadata-formatter.js')
|
|
6
|
+
).href;
|
|
7
|
+
const worldParameters = {
|
|
8
|
+
outputDirectory: outputDir,<% if (isPlaywright) { %>
|
|
9
|
+
baseUrl: '<%= baseUrl %>',
|
|
10
|
+
disableVideoFiles: true,
|
|
11
|
+
disableVideoAttachments: true,<% } %>
|
|
12
|
+
};
|
|
3
13
|
module.exports = {
|
|
4
14
|
default: {
|
|
5
15
|
paths: ['<%= projectRoot %>/features/**/*.feature'],
|
|
@@ -13,15 +23,17 @@ module.exports = {
|
|
|
13
23
|
'summary',
|
|
14
24
|
'progress-bar',
|
|
15
25
|
`json:${outputDir}/reports/cucumber-json/cucumber-test-report.json`,
|
|
26
|
+
`message:${outputDir}/reports/cucumber-messages/cucumber-messages.ndjson`,
|
|
16
27
|
`html:${outputDir}/reports/html/cucumber-test-report-builtin.html`,
|
|
28
|
+
[runMetadataFormatter, `${outputDir}/reports/metadata/cucumber-run-metadata.json`],
|
|
17
29
|
`junit:${outputDir}/reports/test-results/cucumber-test-report.xml`,
|
|
18
30
|
],
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
disableVideoAttachments: true,<% } %>
|
|
31
|
+
formatOptions: {
|
|
32
|
+
nxCucumberRunMetadata: {
|
|
33
|
+
worldParameters,
|
|
34
|
+
},
|
|
24
35
|
},
|
|
36
|
+
worldParameters,
|
|
25
37
|
publishQuiet: true,
|
|
26
38
|
},
|
|
27
39
|
};
|