@gnuechtel/nx-cucumber 2.0.0-beta.2 → 2.0.0-beta.4
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 +114 -4
- package/migrations.json +10 -0
- package/package.json +6 -3
- package/src/core/web-server.d.ts +10 -7
- package/src/core/web-server.js +63 -15
- package/src/core/web-server.js.map +1 -1
- package/src/generators/project/files/all/cucumber.js.template +0 -7
- package/src/generators/project/project.js +55 -5
- package/src/generators/project/project.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -1
- 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 +206 -0
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.js.map +1 -0
- package/src/playwright/playwright-hooks.js +47 -11
- package/src/playwright/playwright-hooks.js.map +1 -1
package/README.md
CHANGED
|
@@ -151,7 +151,13 @@ The generator creates:
|
|
|
151
151
|
- A minimal `project.json` with lint target and implicit dependencies
|
|
152
152
|
- Feature files, step definitions, and TypeScript configuration
|
|
153
153
|
|
|
154
|
-
The `e2e` target
|
|
154
|
+
The `e2e` target behavior depends on the selected test runner:
|
|
155
|
+
|
|
156
|
+
- **Core runner:** no explicit `e2e` target is generated. The `e2e` task is inferred by the plugin from `cucumber.js`.
|
|
157
|
+
- **Playwright runner:** explicit command targets are generated:
|
|
158
|
+
- `e2e-prepare` installs Playwright Chromium (Linux uses `--with-deps`).
|
|
159
|
+
- `e2e` runs `start-server-and-test` with `kill-port` before Cucumber execution.
|
|
160
|
+
- `e2e-ci` runs `e2e-prepare` and then `e2e`.
|
|
155
161
|
|
|
156
162
|
### Additional project generator options
|
|
157
163
|
|
|
@@ -161,12 +167,18 @@ All possible project generator options may be passed as command-line parameters
|
|
|
161
167
|
|
|
162
168
|
### Inferred tasks
|
|
163
169
|
|
|
164
|
-
In 2.x, the `e2e` target is automatically
|
|
170
|
+
In 2.x, no executor configuration is needed. For core projects, the `e2e` target is inferred automatically when the plugin detects a `cucumber.js` config file in a project directory:
|
|
165
171
|
|
|
166
172
|
```bash
|
|
167
173
|
npx nx e2e my-project-e2e
|
|
168
174
|
```
|
|
169
175
|
|
|
176
|
+
For Playwright projects generated by this plugin, explicit command targets are created:
|
|
177
|
+
|
|
178
|
+
- `e2e-prepare`: browser setup
|
|
179
|
+
- `e2e`: test execution with managed web-server lifecycle
|
|
180
|
+
- `e2e-ci`: `e2e-prepare` + `e2e`
|
|
181
|
+
|
|
170
182
|
### Plugin registration
|
|
171
183
|
|
|
172
184
|
To enable inferred tasks, register the plugin in `nx.json`:
|
|
@@ -414,12 +426,15 @@ PWDEBUG=1 npx nx e2e my-project-e2e
|
|
|
414
426
|
|
|
415
427
|
### Watching
|
|
416
428
|
|
|
417
|
-
|
|
429
|
+
There is no dedicated watch mode in `@gnuechtel/nx-cucumber`.
|
|
430
|
+
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:
|
|
418
431
|
|
|
419
432
|
```bash
|
|
420
433
|
npx nx watch --projects=my-project-e2e -- npx nx e2e my-project-e2e
|
|
421
434
|
```
|
|
422
435
|
|
|
436
|
+
This command belongs to Nx workspace tooling, not to a plugin-specific watch implementation.
|
|
437
|
+
|
|
423
438
|
## Filter Cucumber scenarios
|
|
424
439
|
|
|
425
440
|
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.
|
|
@@ -534,6 +549,32 @@ Since 2.x uses framework-native execution via `npx cucumber-js`, all Cucumber CL
|
|
|
534
549
|
|
|
535
550
|
If a feature is missing, please open an issue with a minimal reproducible setup.
|
|
536
551
|
|
|
552
|
+
### Windows Playwright jobs hang or time out unexpectedly
|
|
553
|
+
|
|
554
|
+
If Windows jobs appear to hang after scenarios finish, or fail intermittently before the first browser step, use the generated Playwright `e2e` command pattern:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
npx kill-port 4200 && start-server-and-test "npx nx run <app>:serve --port=4200 --strictPort" http://localhost:4200 "npx cucumber-js --config <e2e-project>/cucumber.js"
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
For CI, prefer the generated `e2e-ci` target to include browser preparation:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
npx nx run <e2e-project>:e2e-ci
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Why this is more stable:
|
|
567
|
+
|
|
568
|
+
- `kill-port` removes stale listeners before readiness checks.
|
|
569
|
+
- `start-server-and-test` owns start/wait/test/stop lifecycle in one process chain.
|
|
570
|
+
- `--strictPort` prevents fallback port switches by Vite that can break URL expectations.
|
|
571
|
+
|
|
572
|
+
For this repository, use the dedicated regression command:
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
npm run e2e:port-regression:react-example
|
|
576
|
+
```
|
|
577
|
+
|
|
537
578
|
## Migration from 1.x
|
|
538
579
|
|
|
539
580
|
### Prerequisites
|
|
@@ -541,6 +582,63 @@ If a feature is missing, please open an issue with a minimal reproducible setup.
|
|
|
541
582
|
- Upgrade to Nx `>=22`
|
|
542
583
|
- Install `@gnuechtel/nx-cucumber@2`
|
|
543
584
|
|
|
585
|
+
### Version compatibility and upgrade order
|
|
586
|
+
|
|
587
|
+
`@gnuechtel/nx-cucumber` 1.x does not have one single Nx compatibility range (taken from published npm `peerDependencies`):
|
|
588
|
+
|
|
589
|
+
| nx-cucumber 1.x | Nx peer compatibility |
|
|
590
|
+
| --------------- | ------------------------- |
|
|
591
|
+
| `1.0.0` | `^16 \|\| ^17` |
|
|
592
|
+
| `1.0.1` | `^16 \|\| ^17 \|\| ^18` |
|
|
593
|
+
| `1.0.2` | `16 - 19` |
|
|
594
|
+
| `1.1.0` | `>=19 <23` |
|
|
595
|
+
| `1.2.0 - 1.4.1` | `>=20 <23 \|\| ^22.0.0-0` |
|
|
596
|
+
|
|
597
|
+
Recommended upgrade order for projects moving from 1.x to 2.x:
|
|
598
|
+
|
|
599
|
+
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.
|
|
600
|
+
2. Remove the legacy plugin package from `package.json`:
|
|
601
|
+
`npm remove @gnuechtel/nx-cucumber`
|
|
602
|
+
3. Upgrade Nx to 22:
|
|
603
|
+
`npx nx@22.6.4 migrate 22.6.4`
|
|
604
|
+
`npm install`
|
|
605
|
+
`npx nx migrate --run-migrations`
|
|
606
|
+
4. Install `@gnuechtel/nx-cucumber@2`.
|
|
607
|
+
5. Run the plugin migration (`nx migrate @gnuechtel/nx-cucumber@2` + `nx migrate --run-migrations`).
|
|
608
|
+
|
|
609
|
+
For `1.1.0+`, Nx 22 is already within the supported peer range. The temporary Nx-17 compatibility step is not required there.
|
|
610
|
+
|
|
611
|
+
### Automated migration via nx migrate
|
|
612
|
+
|
|
613
|
+
You can run the legacy executor migration as part of the standard Nx migration flow:
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
npx nx migrate @gnuechtel/nx-cucumber@2
|
|
617
|
+
npx nx migrate --run-migrations
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
What this migration does:
|
|
621
|
+
|
|
622
|
+
- Detects projects using `@gnuechtel/nx-cucumber:cucumber-api`.
|
|
623
|
+
- Adds `@gnuechtel/nx-cucumber` to `nx.json` plugins (if missing).
|
|
624
|
+
- Removes legacy executor targets (`e2e-cucumber`, and legacy `e2e` wrappers pointing to `e2e-cucumber`).
|
|
625
|
+
- Removes legacy `e2e-watch` targets that use the old `--watch-message` run-command flow.
|
|
626
|
+
- Creates a `cucumber.js` file when missing, based on detected legacy options.
|
|
627
|
+
- Writes a migration report to `tmp/nx-cucumber-migration-report.json`.
|
|
628
|
+
|
|
629
|
+
Dry-run mode:
|
|
630
|
+
|
|
631
|
+
- Set `NX_CUCUMBER_MIGRATION_DRY_RUN=true` before running migrations to print planned changes without modifying files.
|
|
632
|
+
|
|
633
|
+
### CI migration tests
|
|
634
|
+
|
|
635
|
+
The repository includes an automated migration matrix test (`@migration` scenarios) for all stable 1.x versions (`1.0.0` to `1.4.1`).
|
|
636
|
+
|
|
637
|
+
- 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.
|
|
638
|
+
- For `1.1.0+`, the test runs directly on Nx 22.
|
|
639
|
+
|
|
640
|
+
`npm install` failures from legacy generator internals are not treated as acceptable in these migration tests.
|
|
641
|
+
|
|
544
642
|
### Step 1: Register the plugin
|
|
545
643
|
|
|
546
644
|
Add the plugin to `nx.json`:
|
|
@@ -584,7 +682,7 @@ For core (non-Playwright) projects, omit the Playwright require entries.
|
|
|
584
682
|
|
|
585
683
|
### Step 3: Simplify project.json
|
|
586
684
|
|
|
587
|
-
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`.
|
|
685
|
+
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`.
|
|
588
686
|
|
|
589
687
|
### Step 4: Move executor options to cucumber.js
|
|
590
688
|
|
|
@@ -607,3 +705,15 @@ npx nx e2e my-project-e2e
|
|
|
607
705
|
```
|
|
608
706
|
|
|
609
707
|
The inferred target runs `npx cucumber-js` with the configuration from `cucumber.js`.
|
|
708
|
+
|
|
709
|
+
### Rollback strategy
|
|
710
|
+
|
|
711
|
+
- Revert the migration commit in git if validation fails.
|
|
712
|
+
- Remove `@gnuechtel/nx-cucumber` from `nx.json` plugins if rollback to 1.x is required.
|
|
713
|
+
- Restore previous `project.json` executor targets and `cucumber.js` from version control.
|
|
714
|
+
|
|
715
|
+
### Known limitations
|
|
716
|
+
|
|
717
|
+
- Existing `cucumber.js` files are not overwritten automatically.
|
|
718
|
+
- Framework-native watch mode is not generated automatically. If you still need watch, add your own adapter-native watch command after migration.
|
|
719
|
+
- 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-beta.4",
|
|
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-beta.
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
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-beta.
|
|
16
|
-
"@gnuechtel/shared-util": "2.0.0-beta.
|
|
15
|
+
"@gnuechtel/shared-coverage": "2.0.0-beta.4",
|
|
16
|
+
"@gnuechtel/shared-util": "2.0.0-beta.4"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"@cucumber/cucumber": ">=9 <13",
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"type": "commonjs",
|
|
34
34
|
"main": "./src/index.js",
|
|
35
35
|
"generators": "./generators.json",
|
|
36
|
+
"nx-migrations": {
|
|
37
|
+
"migrations": "./migrations.json"
|
|
38
|
+
},
|
|
36
39
|
"executors": "./executors.json",
|
|
37
40
|
"typings": "./src/index.d.ts",
|
|
38
41
|
"types": "./src/index.d.ts"
|
package/src/core/web-server.d.ts
CHANGED
|
@@ -8,19 +8,22 @@ export interface WebServerConfig {
|
|
|
8
8
|
/** Environment variables for the server process. Defaults to the current process environment. */
|
|
9
9
|
env?: NodeJS.ProcessEnv;
|
|
10
10
|
/**
|
|
11
|
-
* stdio mode for the server process.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* stdio mode for the server process. Defaults to 'ignore'.
|
|
12
|
+
*
|
|
13
|
+
* Keep the default 'ignore' when calling startWebServer at module load time or from any
|
|
14
|
+
* synchronous context. waitForUrl uses Atomics.wait which blocks the entire Node.js event
|
|
15
|
+
* loop - stdout data handlers cannot fire, so the pipe buffer fills up. On Windows the
|
|
16
|
+
* pipe buffer is only 4 KB: Vite fills it during startup output, blocks on write, and
|
|
17
|
+
* never starts the HTTP server, causing an indefinite hang.
|
|
18
|
+
*
|
|
19
|
+
* Use 'pipe' only when the event loop is free to process I/O during the wait (e.g. when
|
|
20
|
+
* called from an async BeforeAll hook without Atomics.wait).
|
|
14
21
|
*/
|
|
15
22
|
stdio?: 'pipe' | 'ignore';
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
19
26
|
* Mirrors Playwright's webServer config pattern.
|
|
20
|
-
*
|
|
21
|
-
* Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),
|
|
22
|
-
* set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally
|
|
23
|
-
* before invoking cucumber and rely on `reuseExistingServer`.
|
|
24
27
|
*/
|
|
25
28
|
export declare function startWebServer(config: WebServerConfig): void;
|
|
26
29
|
export declare function stopWebServer(): void;
|
package/src/core/web-server.js
CHANGED
|
@@ -5,20 +5,18 @@ exports.stopWebServer = stopWebServer;
|
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const shared_util_1 = require("@gnuechtel/shared-util");
|
|
7
7
|
let serverProcess;
|
|
8
|
+
let serverPort;
|
|
8
9
|
/**
|
|
9
10
|
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
10
11
|
* Mirrors Playwright's webServer config pattern.
|
|
11
|
-
*
|
|
12
|
-
* Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),
|
|
13
|
-
* set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally
|
|
14
|
-
* before invoking cucumber and rely on `reuseExistingServer`.
|
|
15
12
|
*/
|
|
16
13
|
function startWebServer(config) {
|
|
17
|
-
const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = '
|
|
14
|
+
const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'ignore' } = config;
|
|
18
15
|
if (reuseExistingServer && isUrlReachable(url)) {
|
|
19
16
|
console.log(`Reusing existing server at ${url}`);
|
|
20
17
|
return;
|
|
21
18
|
}
|
|
19
|
+
serverPort = extractPort(url);
|
|
22
20
|
console.log(`Starting web server: ${command}`);
|
|
23
21
|
serverProcess = (0, child_process_1.spawn)(command, {
|
|
24
22
|
shell: true,
|
|
@@ -36,16 +34,13 @@ function startWebServer(config) {
|
|
|
36
34
|
});
|
|
37
35
|
}
|
|
38
36
|
serverProcess.unref();
|
|
39
|
-
process.on('exit',
|
|
37
|
+
process.on('exit', stopWebServerOnProcessExit);
|
|
40
38
|
process.on('SIGINT', stopWebServer);
|
|
41
39
|
process.on('SIGTERM', stopWebServer);
|
|
42
40
|
console.log(`Waiting for ${url}...`);
|
|
43
41
|
waitForUrl(url, timeout);
|
|
44
42
|
console.log(`Server ready at ${url}`);
|
|
45
43
|
if (stdio === 'pipe') {
|
|
46
|
-
// Unref the stdio streams so they don't prevent the parent process from exiting
|
|
47
|
-
// once the tests complete. The streams are still active and will forward output,
|
|
48
|
-
// but the event loop won't be kept alive waiting for them.
|
|
49
44
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
45
|
serverProcess.stdout?.unref?.();
|
|
51
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -56,9 +51,31 @@ function stopWebServer() {
|
|
|
56
51
|
if (!serverProcess || serverProcess.killed)
|
|
57
52
|
return;
|
|
58
53
|
const pid = serverProcess.pid;
|
|
54
|
+
const port = serverPort;
|
|
59
55
|
serverProcess = undefined;
|
|
56
|
+
serverPort = undefined;
|
|
60
57
|
if (pid)
|
|
61
58
|
(0, shared_util_1.killProcessTree)(pid);
|
|
59
|
+
if (process.platform === 'win32' && port) {
|
|
60
|
+
killProcessOnPort(port);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function stopWebServerOnProcessExit() {
|
|
64
|
+
if (!serverProcess || serverProcess.killed)
|
|
65
|
+
return;
|
|
66
|
+
const pid = serverProcess.pid;
|
|
67
|
+
serverProcess = undefined;
|
|
68
|
+
serverPort = undefined;
|
|
69
|
+
if (!pid)
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
// Avoid synchronous process-tree cleanup on process exit to prevent rare
|
|
73
|
+
// Windows deadlocks while the Node.js process is shutting down.
|
|
74
|
+
process.kill(pid, 'SIGTERM');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// ignore - process may have already exited
|
|
78
|
+
}
|
|
62
79
|
}
|
|
63
80
|
function waitForUrl(url, timeout) {
|
|
64
81
|
const interval = 1000;
|
|
@@ -77,15 +94,46 @@ function sleepSync(ms) {
|
|
|
77
94
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
78
95
|
}
|
|
79
96
|
function isUrlReachable(url) {
|
|
97
|
+
if (process.platform === 'win32') {
|
|
98
|
+
return isUrlReachableOnWindows(url);
|
|
99
|
+
}
|
|
100
|
+
// Let the child process exit itself after 2s instead of relying on spawnSync timeout.
|
|
101
|
+
// On Windows this path is avoided because GitLab Windows runners showed rare hangs with
|
|
102
|
+
// node+fetch probes. The PowerShell probe below has a parent timeout as a hard guard.
|
|
103
|
+
const result = (0, child_process_1.spawnSync)('node', [
|
|
104
|
+
'-e',
|
|
105
|
+
`setTimeout(()=>process.exit(1),2000);fetch('${url}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`,
|
|
106
|
+
], { stdio: 'ignore' });
|
|
107
|
+
return result.status === 0;
|
|
108
|
+
}
|
|
109
|
+
function isUrlReachableOnWindows(url) {
|
|
110
|
+
const escapedUrl = url.replace(/'/g, "''");
|
|
111
|
+
const result = (0, child_process_1.spawnSync)('powershell', [
|
|
112
|
+
'-NoProfile',
|
|
113
|
+
'-NonInteractive',
|
|
114
|
+
'-Command',
|
|
115
|
+
`$ProgressPreference='SilentlyContinue'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '${escapedUrl}' -TimeoutSec 2; if ($r.StatusCode -ge 200 -and $r.StatusCode -lt 400) { exit 0 } else { exit 1 } } catch { exit 1 }`,
|
|
116
|
+
], { stdio: 'ignore', timeout: 10000 });
|
|
117
|
+
return result.status === 0;
|
|
118
|
+
}
|
|
119
|
+
function extractPort(url) {
|
|
80
120
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return
|
|
121
|
+
const parsed = new URL(url);
|
|
122
|
+
const port = parseInt(parsed.port, 10);
|
|
123
|
+
if (port > 0)
|
|
124
|
+
return port;
|
|
125
|
+
return parsed.protocol === 'https:' ? 443 : 80;
|
|
86
126
|
}
|
|
87
127
|
catch {
|
|
88
|
-
return
|
|
128
|
+
return undefined;
|
|
89
129
|
}
|
|
90
130
|
}
|
|
131
|
+
function killProcessOnPort(port) {
|
|
132
|
+
(0, child_process_1.spawnSync)('powershell', [
|
|
133
|
+
'-NoProfile',
|
|
134
|
+
'-NonInteractive',
|
|
135
|
+
'-Command',
|
|
136
|
+
`$conns = Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue; foreach ($c in $conns) { taskkill /F /T /PID $c.OwningProcess 2>$null }`,
|
|
137
|
+
], { stdio: 'ignore', timeout: 10000 });
|
|
138
|
+
}
|
|
91
139
|
//# sourceMappingURL=web-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-server.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/core/web-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from 'child_process';\nimport { killProcessTree } from '@gnuechtel/shared-util';\n\nexport interface WebServerConfig {\n command: string;\n url: string;\n reuseExistingServer?: boolean;\n timeout?: number;\n /** Working directory for the server process. Defaults to the current working directory. */\n cwd?: string;\n /** Environment variables for the server process. Defaults to the current process environment. */\n env?: NodeJS.ProcessEnv;\n /**\n * stdio mode for the server process.\n * Use 'pipe' (default) to forward server output prefixed with '[serve]'.\n * Use 'ignore' when starting the server inside a spawnSync context to avoid pipe-buffer deadlocks.\n */\n stdio?: 'pipe' | 'ignore';\n}\n\nlet serverProcess: ReturnType<typeof spawn> | undefined;\n\n/**\n * Starts a web server before Cucumber runs and stops it when the process exits.\n * Mirrors Playwright's webServer config pattern.\n *\n * Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),\n * set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally\n * before invoking cucumber and rely on `reuseExistingServer`.\n */\nexport function startWebServer(config: WebServerConfig): void {\n const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'pipe' } = config;\n\n if (reuseExistingServer && isUrlReachable(url)) {\n console.log(`Reusing existing server at ${url}`);\n return;\n }\n\n console.log(`Starting web server: ${command}`);\n serverProcess = spawn(command, {\n shell: true,\n stdio,\n detached: true,\n ...(cwd ? { cwd } : {}),\n ...(env ? { env } : {}),\n });\n\n if (stdio === 'pipe') {\n serverProcess.stdout?.on('data', (data: Buffer) => {\n process.stdout.write(`[serve] ${data}`);\n });\n serverProcess.stderr?.on('data', (data: Buffer) => {\n process.stderr.write(`[serve] ${data}`);\n });\n }\n\n serverProcess.unref();\n\n process.on('exit', stopWebServer);\n process.on('SIGINT', stopWebServer);\n process.on('SIGTERM', stopWebServer);\n\n console.log(`Waiting for ${url}...`);\n waitForUrl(url, timeout);\n console.log(`Server ready at ${url}`);\n\n if (stdio === 'pipe') {\n // Unref the stdio streams so they don't prevent the parent process from exiting\n // once the tests complete. The streams are still active and will forward output,\n // but the event loop won't be kept alive waiting for them.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stdout as any)?.unref?.();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stderr as any)?.unref?.();\n }\n}\n\nexport function stopWebServer(): void {\n if (!serverProcess || serverProcess.killed) return;\n const pid = serverProcess.pid;\n serverProcess = undefined;\n if (pid) killProcessTree(pid);\n}\n\nfunction waitForUrl(url: string, timeout: number): void {\n const interval = 1000;\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (isUrlReachable(url)) return;\n if (serverProcess?.exitCode !== null && serverProcess?.exitCode !== undefined) {\n throw new Error(`Web server exited with code ${serverProcess.exitCode} before becoming ready`);\n }\n sleepSync(interval);\n }\n throw new Error(`Timed out after ${timeout}ms waiting for ${url}`);\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\nfunction isUrlReachable(url: string): boolean {\n try {\n execSync(`node -e \"fetch('${url}').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\"`, {\n stdio: 'ignore',\n timeout: 3000,\n });\n return true;\n } catch {\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"web-server.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/core/web-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync, spawn } from 'child_process';\nimport { killProcessTree } from '@gnuechtel/shared-util';\n\nexport interface WebServerConfig {\n command: string;\n url: string;\n reuseExistingServer?: boolean;\n timeout?: number;\n /** Working directory for the server process. Defaults to the current working directory. */\n cwd?: string;\n /** Environment variables for the server process. Defaults to the current process environment. */\n env?: NodeJS.ProcessEnv;\n /**\n * stdio mode for the server process. Defaults to 'ignore'.\n *\n * Keep the default 'ignore' when calling startWebServer at module load time or from any\n * synchronous context. waitForUrl uses Atomics.wait which blocks the entire Node.js event\n * loop - stdout data handlers cannot fire, so the pipe buffer fills up. On Windows the\n * pipe buffer is only 4 KB: Vite fills it during startup output, blocks on write, and\n * never starts the HTTP server, causing an indefinite hang.\n *\n * Use 'pipe' only when the event loop is free to process I/O during the wait (e.g. when\n * called from an async BeforeAll hook without Atomics.wait).\n */\n stdio?: 'pipe' | 'ignore';\n}\n\nlet serverProcess: ReturnType<typeof spawn> | undefined;\nlet serverPort: number | undefined;\n\n/**\n * Starts a web server before Cucumber runs and stops it when the process exits.\n * Mirrors Playwright's webServer config pattern.\n */\nexport function startWebServer(config: WebServerConfig): void {\n const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'ignore' } = config;\n\n if (reuseExistingServer && isUrlReachable(url)) {\n console.log(`Reusing existing server at ${url}`);\n return;\n }\n\n serverPort = extractPort(url);\n\n console.log(`Starting web server: ${command}`);\n serverProcess = spawn(command, {\n shell: true,\n stdio,\n detached: true,\n ...(cwd ? { cwd } : {}),\n ...(env ? { env } : {}),\n });\n\n if (stdio === 'pipe') {\n serverProcess.stdout?.on('data', (data: Buffer) => {\n process.stdout.write(`[serve] ${data}`);\n });\n serverProcess.stderr?.on('data', (data: Buffer) => {\n process.stderr.write(`[serve] ${data}`);\n });\n }\n\n serverProcess.unref();\n\n process.on('exit', stopWebServerOnProcessExit);\n process.on('SIGINT', stopWebServer);\n process.on('SIGTERM', stopWebServer);\n\n console.log(`Waiting for ${url}...`);\n waitForUrl(url, timeout);\n console.log(`Server ready at ${url}`);\n\n if (stdio === 'pipe') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stdout as any)?.unref?.();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stderr as any)?.unref?.();\n }\n}\n\nexport function stopWebServer(): void {\n if (!serverProcess || serverProcess.killed) return;\n const pid = serverProcess.pid;\n const port = serverPort;\n serverProcess = undefined;\n serverPort = undefined;\n if (pid) killProcessTree(pid);\n if (process.platform === 'win32' && port) {\n killProcessOnPort(port);\n }\n}\n\nfunction stopWebServerOnProcessExit(): void {\n if (!serverProcess || serverProcess.killed) return;\n const pid = serverProcess.pid;\n serverProcess = undefined;\n serverPort = undefined;\n if (!pid) return;\n try {\n // Avoid synchronous process-tree cleanup on process exit to prevent rare\n // Windows deadlocks while the Node.js process is shutting down.\n process.kill(pid, 'SIGTERM');\n } catch {\n // ignore - process may have already exited\n }\n}\n\nfunction waitForUrl(url: string, timeout: number): void {\n const interval = 1000;\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (isUrlReachable(url)) return;\n if (serverProcess?.exitCode !== null && serverProcess?.exitCode !== undefined) {\n throw new Error(`Web server exited with code ${serverProcess.exitCode} before becoming ready`);\n }\n sleepSync(interval);\n }\n throw new Error(`Timed out after ${timeout}ms waiting for ${url}`);\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\nfunction isUrlReachable(url: string): boolean {\n if (process.platform === 'win32') {\n return isUrlReachableOnWindows(url);\n }\n\n // Let the child process exit itself after 2s instead of relying on spawnSync timeout.\n // On Windows this path is avoided because GitLab Windows runners showed rare hangs with\n // node+fetch probes. The PowerShell probe below has a parent timeout as a hard guard.\n const result = spawnSync(\n 'node',\n [\n '-e',\n `setTimeout(()=>process.exit(1),2000);fetch('${url}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`,\n ],\n { stdio: 'ignore' }\n );\n return result.status === 0;\n}\n\nfunction isUrlReachableOnWindows(url: string): boolean {\n const escapedUrl = url.replace(/'/g, \"''\");\n const result = spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `$ProgressPreference='SilentlyContinue'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '${escapedUrl}' -TimeoutSec 2; if ($r.StatusCode -ge 200 -and $r.StatusCode -lt 400) { exit 0 } else { exit 1 } } catch { exit 1 }`,\n ],\n { stdio: 'ignore', timeout: 10000 }\n );\n return result.status === 0;\n}\n\nfunction extractPort(url: string): number | undefined {\n try {\n const parsed = new URL(url);\n const port = parseInt(parsed.port, 10);\n if (port > 0) return port;\n return parsed.protocol === 'https:' ? 443 : 80;\n } catch {\n return undefined;\n }\n}\n\nfunction killProcessOnPort(port: number): void {\n spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `$conns = Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue; foreach ($c in $conns) { taskkill /F /T /PID $c.OwningProcess 2>$null }`,\n ],\n { stdio: 'ignore', timeout: 10000 }\n );\n}\n"]}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
process.env.TS_NODE_PROJECT = '<%= projectRoot %>/tsconfig.json';
|
|
2
2
|
const outputDir = 'dist/e2e/<%= projectRoot %>';
|
|
3
|
-
<% if (isPlaywright) { %>
|
|
4
|
-
const { startWebServer } = require('@gnuechtel/nx-cucumber');
|
|
5
|
-
startWebServer({
|
|
6
|
-
command: 'npx nx run <%= serveTarget %>',
|
|
7
|
-
url: '<%= baseUrl %>',
|
|
8
|
-
});
|
|
9
|
-
<% } %>
|
|
10
3
|
module.exports = {
|
|
11
4
|
default: {
|
|
12
5
|
paths: ['<%= projectRoot %>/features/**/*.feature'],
|
|
@@ -13,12 +13,18 @@ async function cucumberJsGenerator(tree, options) {
|
|
|
13
13
|
options.directory = options.name;
|
|
14
14
|
if (!options.testRunner)
|
|
15
15
|
options.testRunner = test_run_options_1.defaultTestRunner;
|
|
16
|
+
const isPlaywright = options.testRunner === 'playwright';
|
|
17
|
+
const baseUrl = isPlaywright ? 'http://localhost:4200' : undefined;
|
|
18
|
+
const serveTarget = isPlaywright ? `${options.project}:serve` : undefined;
|
|
16
19
|
const normalizedDirectory = (0, devkit_1.normalizePath)(options.directory);
|
|
17
20
|
const workspaceLayout = (0, devkit_1.getWorkspaceLayout)(tree);
|
|
18
21
|
const skipAppsDir = workspaceLayout.appsDir === '.' || options.ignoreWorkspaceLayout;
|
|
19
22
|
const parentDir = workspaceLayout.appsDir && !skipAppsDir ? workspaceLayout.appsDir + '/' : '';
|
|
20
23
|
const projectRoot = `${parentDir}${normalizedDirectory}`;
|
|
21
24
|
const workspaceRootRelative = (0, devkit_1.offsetFromRoot)(projectRoot).replace(/\/$/, '');
|
|
25
|
+
const playwrightTargets = isPlaywright && baseUrl && serveTarget
|
|
26
|
+
? buildPlaywrightTargets(tree, options.project, options.name, serveTarget, baseUrl, projectRoot)
|
|
27
|
+
: {};
|
|
22
28
|
(0, devkit_1.addProjectConfiguration)(tree, options.name, {
|
|
23
29
|
root: projectRoot,
|
|
24
30
|
sourceRoot: `${projectRoot}/src`,
|
|
@@ -31,6 +37,7 @@ async function cucumberJsGenerator(tree, options) {
|
|
|
31
37
|
lintFilePatterns: [`${projectRoot}/**/*.ts`],
|
|
32
38
|
},
|
|
33
39
|
},
|
|
40
|
+
...playwrightTargets,
|
|
34
41
|
},
|
|
35
42
|
implicitDependencies: [options.project],
|
|
36
43
|
});
|
|
@@ -40,17 +47,14 @@ async function cucumberJsGenerator(tree, options) {
|
|
|
40
47
|
'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-hooks.js',
|
|
41
48
|
]
|
|
42
49
|
: [];
|
|
43
|
-
const isPlaywright = options.testRunner === 'playwright';
|
|
44
|
-
const baseUrl = isPlaywright ? 'http://localhost:4200' : '';
|
|
45
|
-
const serveTarget = isPlaywright ? `${options.project}:serve` : '';
|
|
46
50
|
const substitutions = {
|
|
47
51
|
...options,
|
|
48
52
|
workspaceRootRelative,
|
|
49
53
|
projectRoot,
|
|
50
54
|
playwrightRequires: JSON.stringify(playwrightRequires),
|
|
51
55
|
isPlaywright,
|
|
52
|
-
baseUrl,
|
|
53
|
-
serveTarget,
|
|
56
|
+
baseUrl: baseUrl || '',
|
|
57
|
+
serveTarget: serveTarget || '',
|
|
54
58
|
};
|
|
55
59
|
(0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files', 'all'), projectRoot, substitutions);
|
|
56
60
|
(0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files', options.testRunner), projectRoot, substitutions);
|
|
@@ -63,8 +67,54 @@ async function cucumberJsGenerator(tree, options) {
|
|
|
63
67
|
'ts-node': '10.9.1',
|
|
64
68
|
'tsconfig-paths': '4.2.0',
|
|
65
69
|
};
|
|
70
|
+
if (isPlaywright) {
|
|
71
|
+
additionalDevDependencies['kill-port'] = '2.0.1';
|
|
72
|
+
additionalDevDependencies['start-server-and-test'] = '3.0.0';
|
|
73
|
+
}
|
|
66
74
|
const installTask = (0, devkit_1.addDependenciesToPackageJson)(tree, {}, additionalDevDependencies);
|
|
67
75
|
return options.skipInstall ? () => undefined : installTask;
|
|
68
76
|
}
|
|
77
|
+
function buildPlaywrightTargets(tree, projectName, e2eProjectName, serveTarget, baseUrl, projectRoot) {
|
|
78
|
+
const prepareCommand = buildPlaywrightPrepareCommand();
|
|
79
|
+
const e2eCommand = buildPlaywrightE2eCommand(tree, projectName, serveTarget, baseUrl, projectRoot);
|
|
80
|
+
const e2eCiCommand = `npx nx run ${e2eProjectName}:e2e-prepare && npx nx run ${e2eProjectName}:e2e`;
|
|
81
|
+
return {
|
|
82
|
+
'e2e-prepare': {
|
|
83
|
+
command: prepareCommand,
|
|
84
|
+
},
|
|
85
|
+
e2e: {
|
|
86
|
+
command: e2eCommand,
|
|
87
|
+
},
|
|
88
|
+
'e2e-ci': {
|
|
89
|
+
command: e2eCiCommand,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function buildPlaywrightPrepareCommand() {
|
|
94
|
+
return `node -e "const { execSync } = require('node:child_process'); const installCommand = process.platform === 'linux' ? 'npx playwright install --with-deps chromium' : 'npx playwright install chromium'; execSync(installCommand, { stdio: 'inherit' });"`;
|
|
95
|
+
}
|
|
96
|
+
function buildPlaywrightE2eCommand(tree, projectName, serveTarget, baseUrl, projectRoot) {
|
|
97
|
+
const port = readPortFromUrl(baseUrl);
|
|
98
|
+
const serveExecutor = readServeExecutor(tree, projectName);
|
|
99
|
+
const serveCommand = serveExecutor === '@nx/vite:dev-server'
|
|
100
|
+
? `npx nx run ${serveTarget} --port=${port} --strictPort`
|
|
101
|
+
: `npx nx run ${serveTarget}`;
|
|
102
|
+
const testCommand = `npx cucumber-js --config ${projectRoot}/cucumber.js`;
|
|
103
|
+
return `npx kill-port ${port} && start-server-and-test "${serveCommand}" ${baseUrl} "${testCommand}"`;
|
|
104
|
+
}
|
|
105
|
+
function readServeExecutor(tree, projectName) {
|
|
106
|
+
try {
|
|
107
|
+
return (0, devkit_1.readProjectConfiguration)(tree, projectName).targets?.['serve']?.executor;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function readPortFromUrl(url) {
|
|
114
|
+
const parsedUrl = new URL(url);
|
|
115
|
+
if (parsedUrl.port)
|
|
116
|
+
return parsedUrl.port;
|
|
117
|
+
return parsedUrl.protocol === 'https:' ? '443' : '80';
|
|
118
|
+
}
|
|
69
119
|
exports.default = cucumberJsGenerator;
|
|
70
120
|
//# sourceMappingURL=project.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../../../../../libs/nx-cucumber/src/generators/project/project.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../../../../../libs/nx-cucumber/src/generators/project/project.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addProjectConfiguration,\n formatFiles,\n generateFiles,\n Tree,\n addDependenciesToPackageJson,\n normalizePath,\n offsetFromRoot,\n getWorkspaceLayout,\n readProjectConfiguration,\n} from '@nx/devkit';\nimport * as path from 'path';\nimport { CucumberJsGeneratorSchema } from './schema';\nimport { existsSync } from 'fs';\nimport { defaultTestRunner } from '../../core/test-run-options';\n\nexport async function cucumberJsGenerator(tree: Tree, options: CucumberJsGeneratorSchema) {\n if (!options.name) options.name = `${options.project}-e2e`;\n if (!options.directory) options.directory = options.name;\n if (!options.testRunner) options.testRunner = defaultTestRunner;\n const isPlaywright = options.testRunner === 'playwright';\n const baseUrl = isPlaywright ? 'http://localhost:4200' : undefined;\n const serveTarget = isPlaywright ? `${options.project}:serve` : undefined;\n\n const normalizedDirectory = normalizePath(options.directory);\n const workspaceLayout = getWorkspaceLayout(tree);\n const skipAppsDir = workspaceLayout.appsDir === '.' || options.ignoreWorkspaceLayout;\n const parentDir = workspaceLayout.appsDir && !skipAppsDir ? workspaceLayout.appsDir + '/' : '';\n const projectRoot = `${parentDir}${normalizedDirectory}`;\n const workspaceRootRelative = offsetFromRoot(projectRoot).replace(/\\/$/, '');\n const playwrightTargets =\n isPlaywright && baseUrl && serveTarget\n ? buildPlaywrightTargets(tree, options.project, options.name, serveTarget, baseUrl, projectRoot)\n : {};\n\n addProjectConfiguration(tree, options.name, {\n root: projectRoot,\n sourceRoot: `${projectRoot}/src`,\n projectType: 'application',\n targets: {\n lint: {\n executor: '@nx/eslint:lint',\n outputs: ['{options.outputFile}'],\n options: {\n lintFilePatterns: [`${projectRoot}/**/*.ts`],\n },\n },\n ...playwrightTargets,\n },\n implicitDependencies: [options.project],\n });\n\n const playwrightRequires =\n options.testRunner === 'playwright'\n ? [\n 'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-world-default.js',\n 'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-hooks.js',\n ]\n : [];\n\n const substitutions = {\n ...options,\n workspaceRootRelative,\n projectRoot,\n playwrightRequires: JSON.stringify(playwrightRequires),\n isPlaywright,\n baseUrl: baseUrl || '',\n serveTarget: serveTarget || '',\n };\n\n generateFiles(tree, path.join(__dirname, 'files', 'all'), projectRoot, substitutions);\n generateFiles(tree, path.join(__dirname, 'files', options.testRunner), projectRoot, substitutions);\n\n const customWorldFiles = path.join(__dirname, 'files', `${options.testRunner}.custom-world`);\n if (options.customWorld && existsSync(customWorldFiles)) {\n generateFiles(tree, customWorldFiles, projectRoot, substitutions);\n }\n\n await formatFiles(tree);\n\n const additionalDevDependencies: Record<string, string> = {\n 'ts-node': '10.9.1',\n 'tsconfig-paths': '4.2.0',\n };\n if (isPlaywright) {\n additionalDevDependencies['kill-port'] = '2.0.1';\n additionalDevDependencies['start-server-and-test'] = '3.0.0';\n }\n\n const installTask = addDependenciesToPackageJson(tree, {}, additionalDevDependencies);\n return options.skipInstall ? () => undefined : installTask;\n}\n\nfunction buildPlaywrightTargets(\n tree: Tree,\n projectName: string,\n e2eProjectName: string,\n serveTarget: string,\n baseUrl: string,\n projectRoot: string\n) {\n const prepareCommand = buildPlaywrightPrepareCommand();\n const e2eCommand = buildPlaywrightE2eCommand(tree, projectName, serveTarget, baseUrl, projectRoot);\n const e2eCiCommand = `npx nx run ${e2eProjectName}:e2e-prepare && npx nx run ${e2eProjectName}:e2e`;\n return {\n 'e2e-prepare': {\n command: prepareCommand,\n },\n e2e: {\n command: e2eCommand,\n },\n 'e2e-ci': {\n command: e2eCiCommand,\n },\n };\n}\n\nfunction buildPlaywrightPrepareCommand() {\n return `node -e \"const { execSync } = require('node:child_process'); const installCommand = process.platform === 'linux' ? 'npx playwright install --with-deps chromium' : 'npx playwright install chromium'; execSync(installCommand, { stdio: 'inherit' });\"`;\n}\n\nfunction buildPlaywrightE2eCommand(\n tree: Tree,\n projectName: string,\n serveTarget: string,\n baseUrl: string,\n projectRoot: string\n) {\n const port = readPortFromUrl(baseUrl);\n const serveExecutor = readServeExecutor(tree, projectName);\n const serveCommand =\n serveExecutor === '@nx/vite:dev-server'\n ? `npx nx run ${serveTarget} --port=${port} --strictPort`\n : `npx nx run ${serveTarget}`;\n const testCommand = `npx cucumber-js --config ${projectRoot}/cucumber.js`;\n return `npx kill-port ${port} && start-server-and-test \"${serveCommand}\" ${baseUrl} \"${testCommand}\"`;\n}\n\nfunction readServeExecutor(tree: Tree, projectName: string) {\n try {\n return readProjectConfiguration(tree, projectName).targets?.['serve']?.executor;\n } catch {\n return undefined;\n }\n}\n\nfunction readPortFromUrl(url: string) {\n const parsedUrl = new URL(url);\n if (parsedUrl.port) return parsedUrl.port;\n return parsedUrl.protocol === 'https:' ? '443' : '80';\n}\n\nexport default cucumberJsGenerator;\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export * from './adapters/validation';
|
|
|
9
9
|
export * from './adapters/registry';
|
|
10
10
|
export { createNodesV2 } from './plugin/create-nodes';
|
|
11
11
|
export type { NxCucumberPluginOptions } from './plugin/create-nodes';
|
|
12
|
+
export { migrateCucumberApiToInferredTasks } from './migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createNodesV2 = exports.stopWebServer = exports.startWebServer = exports.createBeautifulHtmlReport = void 0;
|
|
3
|
+
exports.migrateCucumberApiToInferredTasks = exports.createNodesV2 = exports.stopWebServer = exports.startWebServer = exports.createBeautifulHtmlReport = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
tslib_1.__exportStar(require("./playwright/playwright-world"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./core/test-run-options"), exports);
|
|
@@ -14,4 +14,6 @@ tslib_1.__exportStar(require("./adapters/validation"), exports);
|
|
|
14
14
|
tslib_1.__exportStar(require("./adapters/registry"), exports);
|
|
15
15
|
var create_nodes_1 = require("./plugin/create-nodes");
|
|
16
16
|
Object.defineProperty(exports, "createNodesV2", { enumerable: true, get: function () { return create_nodes_1.createNodesV2; } });
|
|
17
|
+
var migrate_cucumber_api_to_inferred_tasks_1 = require("./migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks");
|
|
18
|
+
Object.defineProperty(exports, "migrateCucumberApiToInferredTasks", { enumerable: true, get: function () { return migrate_cucumber_api_to_inferred_tasks_1.migrateCucumberApiToInferredTasks; } });
|
|
17
19
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/nx-cucumber/src/index.ts"],"names":[],"mappings":";;;;AAAA,wEAA8C;AAC9C,kEAAwC;AACxC,oEAAwE;AAA/D,iIAAA,yBAAyB,OAAA;AAClC,gDAAkE;AAAzD,4GAAA,cAAc,OAAA;AAAE,2GAAA,aAAa,OAAA;AAGtC,8DAAoC;AACpC,gEAAsC;AACtC,8DAAoC;AACpC,sDAAsD;AAA7C,6GAAA,aAAa,OAAA","sourcesContent":["export * from './playwright/playwright-world';\nexport * from './core/test-run-options';\nexport { createBeautifulHtmlReport } from './core/cucumber-report-html';\nexport { startWebServer, stopWebServer } from './core/web-server';\nexport type { WebServerConfig } from './core/web-server';\nexport type { BeautifulReportOptions } from './core/cucumber-report-html';\nexport * from './adapters/contract';\nexport * from './adapters/validation';\nexport * from './adapters/registry';\nexport { createNodesV2 } from './plugin/create-nodes';\nexport type { NxCucumberPluginOptions } from './plugin/create-nodes';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/nx-cucumber/src/index.ts"],"names":[],"mappings":";;;;AAAA,wEAA8C;AAC9C,kEAAwC;AACxC,oEAAwE;AAA/D,iIAAA,yBAAyB,OAAA;AAClC,gDAAkE;AAAzD,4GAAA,cAAc,OAAA;AAAE,2GAAA,aAAa,OAAA;AAGtC,8DAAoC;AACpC,gEAAsC;AACtC,8DAAoC;AACpC,sDAAsD;AAA7C,6GAAA,aAAa,OAAA;AAEtB,2HAAqH;AAA5G,2JAAA,iCAAiC,OAAA","sourcesContent":["export * from './playwright/playwright-world';\nexport * from './core/test-run-options';\nexport { createBeautifulHtmlReport } from './core/cucumber-report-html';\nexport { startWebServer, stopWebServer } from './core/web-server';\nexport type { WebServerConfig } from './core/web-server';\nexport type { BeautifulReportOptions } from './core/cucumber-report-html';\nexport * from './adapters/contract';\nexport * from './adapters/validation';\nexport * from './adapters/registry';\nexport { createNodesV2 } from './plugin/create-nodes';\nexport type { NxCucumberPluginOptions } from './plugin/create-nodes';\nexport { migrateCucumberApiToInferredTasks } from './migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks';\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Tree } from '@nx/devkit';
|
|
2
|
+
interface MigratedProjectReport {
|
|
3
|
+
project: string;
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
removedTargets: string[];
|
|
6
|
+
createdCucumberConfig: boolean;
|
|
7
|
+
manualFollowUps: string[];
|
|
8
|
+
}
|
|
9
|
+
interface MigrationReport {
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
pluginAdded: boolean;
|
|
12
|
+
removedLegacyTargetDefaults: boolean;
|
|
13
|
+
migratedProjects: MigratedProjectReport[];
|
|
14
|
+
}
|
|
15
|
+
interface MigrationOptions {
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function migrateCucumberApiToInferredTasks(tree: Tree, options?: MigrationOptions): Promise<MigrationReport>;
|
|
19
|
+
export default function migrateCucumberApiToInferredTasksEntry(tree: Tree): Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrateCucumberApiToInferredTasks = migrateCucumberApiToInferredTasks;
|
|
4
|
+
exports.default = migrateCucumberApiToInferredTasksEntry;
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const LEGACY_EXECUTOR = '@gnuechtel/nx-cucumber:cucumber-api';
|
|
7
|
+
const REPORT_PATH = 'tmp/nx-cucumber-migration-report.json';
|
|
8
|
+
const PLUGIN_NAME = '@gnuechtel/nx-cucumber';
|
|
9
|
+
function isDryRunRequested() {
|
|
10
|
+
const arg = process.argv.some((v) => v === '--dry-run' || v === '--dryRun');
|
|
11
|
+
const env = `${process.env['NX_CUCUMBER_MIGRATION_DRY_RUN'] || ''}`.toLowerCase();
|
|
12
|
+
return arg || env === '1' || env === 'true' || env === 'yes';
|
|
13
|
+
}
|
|
14
|
+
function normalizePath(p) {
|
|
15
|
+
return p.replace(/\\/g, '/');
|
|
16
|
+
}
|
|
17
|
+
function resolveTsNodeProject(projectRoot, tsConfig) {
|
|
18
|
+
if (!tsConfig)
|
|
19
|
+
return `${projectRoot}/tsconfig.json`;
|
|
20
|
+
if (tsConfig.includes('/'))
|
|
21
|
+
return normalizePath(tsConfig);
|
|
22
|
+
return `${projectRoot}/${tsConfig}`;
|
|
23
|
+
}
|
|
24
|
+
function hasNxCucumberPlugin(nxJson) {
|
|
25
|
+
if (!nxJson)
|
|
26
|
+
return false;
|
|
27
|
+
return (nxJson.plugins?.some((plugin) => typeof plugin === 'string' ? plugin === PLUGIN_NAME : plugin?.plugin === PLUGIN_NAME) ?? false);
|
|
28
|
+
}
|
|
29
|
+
function getLegacyTargetNames(project) {
|
|
30
|
+
return Object.entries(project.targets ?? {})
|
|
31
|
+
.filter(([, target]) => target.executor === LEGACY_EXECUTOR)
|
|
32
|
+
.map(([targetName]) => targetName);
|
|
33
|
+
}
|
|
34
|
+
function pickLegacyOptions(project) {
|
|
35
|
+
const targets = project.targets ?? {};
|
|
36
|
+
const legacyTarget = targets['e2e-cucumber']?.executor === LEGACY_EXECUTOR
|
|
37
|
+
? targets['e2e-cucumber']
|
|
38
|
+
: Object.values(targets).find((target) => target.executor === LEGACY_EXECUTOR);
|
|
39
|
+
return (legacyTarget?.options ?? {});
|
|
40
|
+
}
|
|
41
|
+
function extractWorldParameters(options) {
|
|
42
|
+
const worldParameters = {};
|
|
43
|
+
const allowedKeys = [
|
|
44
|
+
'baseUrl',
|
|
45
|
+
'browser',
|
|
46
|
+
'headed',
|
|
47
|
+
'debug',
|
|
48
|
+
'disableAnimations',
|
|
49
|
+
'disableCoverage',
|
|
50
|
+
'disableScreenshotAttachments',
|
|
51
|
+
'disableScreenshotFiles',
|
|
52
|
+
'enableSuccessfulScreenshotFiles',
|
|
53
|
+
'disableVideoAttachments',
|
|
54
|
+
'disableVideoFiles',
|
|
55
|
+
'disableJavaScript',
|
|
56
|
+
'additionalPlaywrightConfiguration',
|
|
57
|
+
'viewport',
|
|
58
|
+
'videoSize',
|
|
59
|
+
];
|
|
60
|
+
for (const key of allowedKeys) {
|
|
61
|
+
if (options[key] !== undefined) {
|
|
62
|
+
worldParameters[key] = options[key];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return worldParameters;
|
|
66
|
+
}
|
|
67
|
+
function createCucumberConfigContent(projectRoot, options) {
|
|
68
|
+
const outputDir = `dist/e2e/${projectRoot}`;
|
|
69
|
+
const featuresDirectory = normalizePath(options.featuresDirectory || `${projectRoot}/features`);
|
|
70
|
+
const testRunner = options.testRunner === 'playwright' ? 'playwright' : 'core';
|
|
71
|
+
const tsNodeProject = resolveTsNodeProject(projectRoot, typeof options.tsConfig === 'string' ? options.tsConfig : undefined);
|
|
72
|
+
const requireEntries = [`'${projectRoot}/src/**/*.ts'`];
|
|
73
|
+
if (testRunner === 'playwright') {
|
|
74
|
+
requireEntries.push(`'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-world-default.js'`);
|
|
75
|
+
requireEntries.push(`'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-hooks.js'`);
|
|
76
|
+
}
|
|
77
|
+
const worldParameters = {
|
|
78
|
+
outputDirectory: outputDir,
|
|
79
|
+
...extractWorldParameters(options),
|
|
80
|
+
};
|
|
81
|
+
const lines = [
|
|
82
|
+
`process.env.TS_NODE_PROJECT = '${tsNodeProject}';`,
|
|
83
|
+
'module.exports = {',
|
|
84
|
+
' default: {',
|
|
85
|
+
` paths: ['${featuresDirectory}/**/*.feature'],`,
|
|
86
|
+
` requireModule: ['ts-node/register', 'tsconfig-paths/register'],`,
|
|
87
|
+
` require: [${requireEntries.join(', ')}],`,
|
|
88
|
+
` tags: '${typeof options.tags === 'string' ? options.tags : 'not @ignore'}',`,
|
|
89
|
+
' format: [',
|
|
90
|
+
` 'summary',`,
|
|
91
|
+
` 'progress-bar',`,
|
|
92
|
+
` 'json:dist/e2e/${projectRoot}/reports/cucumber-json/cucumber-test-report.json',`,
|
|
93
|
+
` 'html:dist/e2e/${projectRoot}/reports/html/cucumber-test-report-builtin.html',`,
|
|
94
|
+
` 'junit:dist/e2e/${projectRoot}/reports/test-results/cucumber-test-report.xml',`,
|
|
95
|
+
' ],',
|
|
96
|
+
` worldParameters: ${JSON.stringify(worldParameters, null, 4).replace(/\n/g, '\n ')},`,
|
|
97
|
+
...(typeof options.cucumberTimeout === 'number' ? [` timeout: ${options.cucumberTimeout},`] : []),
|
|
98
|
+
' publishQuiet: true,',
|
|
99
|
+
' },',
|
|
100
|
+
'};',
|
|
101
|
+
'',
|
|
102
|
+
];
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
function shouldFlagWatchTarget(targets) {
|
|
106
|
+
const watchCommand = targets?.['e2e-watch']?.options?.['commands'];
|
|
107
|
+
if (!Array.isArray(watchCommand))
|
|
108
|
+
return false;
|
|
109
|
+
return watchCommand.some((cmd) => typeof cmd === 'string' && cmd.includes('watch-message'));
|
|
110
|
+
}
|
|
111
|
+
function shouldRemoveLegacyE2eRunCommand(targets) {
|
|
112
|
+
const e2e = targets?.['e2e'];
|
|
113
|
+
if (!e2e || e2e.executor !== 'nx:run-commands')
|
|
114
|
+
return false;
|
|
115
|
+
const command = e2e.options?.['command'];
|
|
116
|
+
return typeof command === 'string' && command.includes('e2e-cucumber');
|
|
117
|
+
}
|
|
118
|
+
async function migrateCucumberApiToInferredTasks(tree, options = {}) {
|
|
119
|
+
const dryRun = options.dryRun ?? isDryRunRequested();
|
|
120
|
+
const nxJson = ((0, devkit_1.readNxJson)(tree) ?? {});
|
|
121
|
+
const report = {
|
|
122
|
+
dryRun,
|
|
123
|
+
pluginAdded: false,
|
|
124
|
+
removedLegacyTargetDefaults: false,
|
|
125
|
+
migratedProjects: [],
|
|
126
|
+
};
|
|
127
|
+
const hadPlugin = hasNxCucumberPlugin(nxJson);
|
|
128
|
+
if (!hadPlugin) {
|
|
129
|
+
report.pluginAdded = true;
|
|
130
|
+
if (!dryRun) {
|
|
131
|
+
nxJson.plugins ??= [];
|
|
132
|
+
nxJson.plugins.push(PLUGIN_NAME);
|
|
133
|
+
(0, devkit_1.updateNxJson)(tree, nxJson);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (nxJson.targetDefaults?.[LEGACY_EXECUTOR]) {
|
|
137
|
+
report.removedLegacyTargetDefaults = true;
|
|
138
|
+
if (!dryRun) {
|
|
139
|
+
delete nxJson.targetDefaults[LEGACY_EXECUTOR];
|
|
140
|
+
(0, devkit_1.updateNxJson)(tree, nxJson);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const [projectName, projectConfig] of (0, devkit_1.getProjects)(tree).entries()) {
|
|
144
|
+
const legacyTargets = getLegacyTargetNames(projectConfig);
|
|
145
|
+
const removeLegacyRunCommandTarget = shouldRemoveLegacyE2eRunCommand(projectConfig.targets);
|
|
146
|
+
const removeLegacyWatchTarget = shouldFlagWatchTarget(projectConfig.targets);
|
|
147
|
+
if (legacyTargets.length === 0 && !removeLegacyRunCommandTarget && !removeLegacyWatchTarget)
|
|
148
|
+
continue;
|
|
149
|
+
const manualFollowUps = [];
|
|
150
|
+
const removedTargets = [...legacyTargets];
|
|
151
|
+
if (removeLegacyRunCommandTarget && !removedTargets.includes('e2e')) {
|
|
152
|
+
removedTargets.push('e2e');
|
|
153
|
+
}
|
|
154
|
+
if (removeLegacyWatchTarget && !removedTargets.includes('e2e-watch')) {
|
|
155
|
+
removedTargets.push('e2e-watch');
|
|
156
|
+
}
|
|
157
|
+
const cucumberConfigPath = `${projectConfig.root}/cucumber.js`;
|
|
158
|
+
const hasCucumberConfig = tree.exists(cucumberConfigPath);
|
|
159
|
+
const legacyOptions = pickLegacyOptions(projectConfig);
|
|
160
|
+
if (hasCucumberConfig) {
|
|
161
|
+
manualFollowUps.push(`Existing ${cucumberConfigPath} was kept. Verify tags/paths/worldParameters still reflect prior executor options.`);
|
|
162
|
+
}
|
|
163
|
+
const updatedProject = {
|
|
164
|
+
...projectConfig,
|
|
165
|
+
targets: {
|
|
166
|
+
...(projectConfig.targets ?? {}),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
for (const targetName of removedTargets) {
|
|
170
|
+
delete updatedProject.targets?.[targetName];
|
|
171
|
+
}
|
|
172
|
+
if (!dryRun) {
|
|
173
|
+
(0, devkit_1.updateProjectConfiguration)(tree, projectName, updatedProject);
|
|
174
|
+
if (!hasCucumberConfig) {
|
|
175
|
+
tree.write(cucumberConfigPath, createCucumberConfigContent(projectConfig.root, legacyOptions));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
report.migratedProjects.push({
|
|
179
|
+
project: projectName,
|
|
180
|
+
projectRoot: projectConfig.root,
|
|
181
|
+
removedTargets,
|
|
182
|
+
createdCucumberConfig: !hasCucumberConfig,
|
|
183
|
+
manualFollowUps,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (!dryRun) {
|
|
187
|
+
tree.write(REPORT_PATH, JSON.stringify(report, null, 2));
|
|
188
|
+
}
|
|
189
|
+
devkit_1.logger.info(`[nx-cucumber migration] migrated projects: ${report.migratedProjects.length}, pluginAdded: ${report.pluginAdded}, dryRun: ${report.dryRun}`);
|
|
190
|
+
if (report.migratedProjects.length > 0) {
|
|
191
|
+
for (const project of report.migratedProjects) {
|
|
192
|
+
devkit_1.logger.info(`[nx-cucumber migration] ${project.project}: removed targets [${project.removedTargets.join(', ')}], created cucumber.js: ${project.createdCucumberConfig}`);
|
|
193
|
+
for (const followUp of project.manualFollowUps) {
|
|
194
|
+
devkit_1.logger.warn(`[nx-cucumber migration] ${project.project}: ${followUp}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!dryRun) {
|
|
199
|
+
devkit_1.logger.info(`[nx-cucumber migration] report written to ${REPORT_PATH}`);
|
|
200
|
+
}
|
|
201
|
+
return report;
|
|
202
|
+
}
|
|
203
|
+
async function migrateCucumberApiToInferredTasksEntry(tree) {
|
|
204
|
+
await migrateCucumberApiToInferredTasks(tree);
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=migrate-cucumber-api-to-inferred-tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-cucumber-api-to-inferred-tasks.js","sourceRoot":"","sources":["../../../../../../libs/nx-cucumber/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n logger,\n NxJsonConfiguration,\n ProjectConfiguration,\n readNxJson,\n Tree,\n updateNxJson,\n updateProjectConfiguration,\n getProjects,\n} from '@nx/devkit';\n\nconst LEGACY_EXECUTOR = '@gnuechtel/nx-cucumber:cucumber-api';\nconst REPORT_PATH = 'tmp/nx-cucumber-migration-report.json';\nconst PLUGIN_NAME = '@gnuechtel/nx-cucumber';\n\ninterface LegacyOptions {\n testRunner?: 'core' | 'playwright';\n featuresDirectory?: string;\n tags?: string;\n cucumberTimeout?: number;\n tsConfig?: string;\n baseUrl?: string;\n browser?: string;\n headed?: boolean;\n debug?: boolean;\n disableAnimations?: boolean;\n disableCoverage?: boolean;\n disableScreenshotAttachments?: boolean;\n disableScreenshotFiles?: boolean;\n enableSuccessfulScreenshotFiles?: boolean;\n disableVideoAttachments?: boolean;\n disableVideoFiles?: boolean;\n disableJavaScript?: boolean;\n additionalPlaywrightConfiguration?: Record<string, unknown>;\n viewport?: Record<string, unknown>;\n videoSize?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface MigratedProjectReport {\n project: string;\n projectRoot: string;\n removedTargets: string[];\n createdCucumberConfig: boolean;\n manualFollowUps: string[];\n}\n\ninterface MigrationReport {\n dryRun: boolean;\n pluginAdded: boolean;\n removedLegacyTargetDefaults: boolean;\n migratedProjects: MigratedProjectReport[];\n}\n\ninterface MigrationOptions {\n dryRun?: boolean;\n}\n\nfunction isDryRunRequested(): boolean {\n const arg = process.argv.some((v) => v === '--dry-run' || v === '--dryRun');\n const env = `${process.env['NX_CUCUMBER_MIGRATION_DRY_RUN'] || ''}`.toLowerCase();\n return arg || env === '1' || env === 'true' || env === 'yes';\n}\n\nfunction normalizePath(p: string): string {\n return p.replace(/\\\\/g, '/');\n}\n\nfunction resolveTsNodeProject(projectRoot: string, tsConfig?: string): string {\n if (!tsConfig) return `${projectRoot}/tsconfig.json`;\n if (tsConfig.includes('/')) return normalizePath(tsConfig);\n return `${projectRoot}/${tsConfig}`;\n}\n\nfunction hasNxCucumberPlugin(nxJson: NxJsonConfiguration | null): boolean {\n if (!nxJson) return false;\n return (\n nxJson.plugins?.some((plugin) =>\n typeof plugin === 'string' ? plugin === PLUGIN_NAME : plugin?.plugin === PLUGIN_NAME\n ) ?? false\n );\n}\n\nfunction getLegacyTargetNames(project: ProjectConfiguration): string[] {\n return Object.entries(project.targets ?? {})\n .filter(([, target]) => target.executor === LEGACY_EXECUTOR)\n .map(([targetName]) => targetName);\n}\n\nfunction pickLegacyOptions(project: ProjectConfiguration): LegacyOptions {\n const targets = project.targets ?? {};\n const legacyTarget =\n targets['e2e-cucumber']?.executor === LEGACY_EXECUTOR\n ? targets['e2e-cucumber']\n : Object.values(targets).find((target) => target.executor === LEGACY_EXECUTOR);\n return (legacyTarget?.options ?? {}) as LegacyOptions;\n}\n\nfunction extractWorldParameters(options: LegacyOptions): Record<string, unknown> {\n const worldParameters: Record<string, unknown> = {};\n const allowedKeys = [\n 'baseUrl',\n 'browser',\n 'headed',\n 'debug',\n 'disableAnimations',\n 'disableCoverage',\n 'disableScreenshotAttachments',\n 'disableScreenshotFiles',\n 'enableSuccessfulScreenshotFiles',\n 'disableVideoAttachments',\n 'disableVideoFiles',\n 'disableJavaScript',\n 'additionalPlaywrightConfiguration',\n 'viewport',\n 'videoSize',\n ];\n\n for (const key of allowedKeys) {\n if (options[key] !== undefined) {\n worldParameters[key] = options[key];\n }\n }\n return worldParameters;\n}\n\nfunction createCucumberConfigContent(projectRoot: string, options: LegacyOptions): string {\n const outputDir = `dist/e2e/${projectRoot}`;\n const featuresDirectory = normalizePath(options.featuresDirectory || `${projectRoot}/features`);\n const testRunner = options.testRunner === 'playwright' ? 'playwright' : 'core';\n const tsNodeProject = resolveTsNodeProject(\n projectRoot,\n typeof options.tsConfig === 'string' ? options.tsConfig : undefined\n );\n\n const requireEntries: string[] = [`'${projectRoot}/src/**/*.ts'`];\n if (testRunner === 'playwright') {\n requireEntries.push(`'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-world-default.js'`);\n requireEntries.push(`'node_modules/@gnuechtel/nx-cucumber/src/playwright/playwright-hooks.js'`);\n }\n\n const worldParameters = {\n outputDirectory: outputDir,\n ...extractWorldParameters(options),\n };\n\n const lines: string[] = [\n `process.env.TS_NODE_PROJECT = '${tsNodeProject}';`,\n 'module.exports = {',\n ' default: {',\n ` paths: ['${featuresDirectory}/**/*.feature'],`,\n ` requireModule: ['ts-node/register', 'tsconfig-paths/register'],`,\n ` require: [${requireEntries.join(', ')}],`,\n ` tags: '${typeof options.tags === 'string' ? options.tags : 'not @ignore'}',`,\n ' format: [',\n ` 'summary',`,\n ` 'progress-bar',`,\n ` 'json:dist/e2e/${projectRoot}/reports/cucumber-json/cucumber-test-report.json',`,\n ` 'html:dist/e2e/${projectRoot}/reports/html/cucumber-test-report-builtin.html',`,\n ` 'junit:dist/e2e/${projectRoot}/reports/test-results/cucumber-test-report.xml',`,\n ' ],',\n ` worldParameters: ${JSON.stringify(worldParameters, null, 4).replace(/\\n/g, '\\n ')},`,\n ...(typeof options.cucumberTimeout === 'number' ? [` timeout: ${options.cucumberTimeout},`] : []),\n ' publishQuiet: true,',\n ' },',\n '};',\n '',\n ];\n return lines.join('\\n');\n}\n\nfunction shouldFlagWatchTarget(targets: ProjectConfiguration['targets']): boolean {\n const watchCommand = targets?.['e2e-watch']?.options?.['commands'];\n if (!Array.isArray(watchCommand)) return false;\n return watchCommand.some((cmd) => typeof cmd === 'string' && cmd.includes('watch-message'));\n}\n\nfunction shouldRemoveLegacyE2eRunCommand(targets: ProjectConfiguration['targets']): boolean {\n const e2e = targets?.['e2e'];\n if (!e2e || e2e.executor !== 'nx:run-commands') return false;\n const command = e2e.options?.['command'];\n return typeof command === 'string' && command.includes('e2e-cucumber');\n}\n\nexport async function migrateCucumberApiToInferredTasks(\n tree: Tree,\n options: MigrationOptions = {}\n): Promise<MigrationReport> {\n const dryRun = options.dryRun ?? isDryRunRequested();\n const nxJson = (readNxJson(tree) ?? {}) as NxJsonConfiguration;\n const report: MigrationReport = {\n dryRun,\n pluginAdded: false,\n removedLegacyTargetDefaults: false,\n migratedProjects: [],\n };\n\n const hadPlugin = hasNxCucumberPlugin(nxJson);\n if (!hadPlugin) {\n report.pluginAdded = true;\n if (!dryRun) {\n nxJson.plugins ??= [];\n nxJson.plugins.push(PLUGIN_NAME);\n updateNxJson(tree, nxJson);\n }\n }\n\n if (nxJson.targetDefaults?.[LEGACY_EXECUTOR]) {\n report.removedLegacyTargetDefaults = true;\n if (!dryRun) {\n delete nxJson.targetDefaults[LEGACY_EXECUTOR];\n updateNxJson(tree, nxJson);\n }\n }\n\n for (const [projectName, projectConfig] of getProjects(tree).entries()) {\n const legacyTargets = getLegacyTargetNames(projectConfig);\n const removeLegacyRunCommandTarget = shouldRemoveLegacyE2eRunCommand(projectConfig.targets);\n const removeLegacyWatchTarget = shouldFlagWatchTarget(projectConfig.targets);\n if (legacyTargets.length === 0 && !removeLegacyRunCommandTarget && !removeLegacyWatchTarget) continue;\n\n const manualFollowUps: string[] = [];\n const removedTargets = [...legacyTargets];\n if (removeLegacyRunCommandTarget && !removedTargets.includes('e2e')) {\n removedTargets.push('e2e');\n }\n if (removeLegacyWatchTarget && !removedTargets.includes('e2e-watch')) {\n removedTargets.push('e2e-watch');\n }\n\n const cucumberConfigPath = `${projectConfig.root}/cucumber.js`;\n const hasCucumberConfig = tree.exists(cucumberConfigPath);\n const legacyOptions = pickLegacyOptions(projectConfig);\n\n if (hasCucumberConfig) {\n manualFollowUps.push(\n `Existing ${cucumberConfigPath} was kept. Verify tags/paths/worldParameters still reflect prior executor options.`\n );\n }\n\n const updatedProject: ProjectConfiguration = {\n ...projectConfig,\n targets: {\n ...(projectConfig.targets ?? {}),\n },\n };\n\n for (const targetName of removedTargets) {\n delete updatedProject.targets?.[targetName];\n }\n\n if (!dryRun) {\n updateProjectConfiguration(tree, projectName, updatedProject);\n if (!hasCucumberConfig) {\n tree.write(cucumberConfigPath, createCucumberConfigContent(projectConfig.root, legacyOptions));\n }\n }\n\n report.migratedProjects.push({\n project: projectName,\n projectRoot: projectConfig.root,\n removedTargets,\n createdCucumberConfig: !hasCucumberConfig,\n manualFollowUps,\n });\n }\n\n if (!dryRun) {\n tree.write(REPORT_PATH, JSON.stringify(report, null, 2));\n }\n\n logger.info(\n `[nx-cucumber migration] migrated projects: ${report.migratedProjects.length}, pluginAdded: ${report.pluginAdded}, dryRun: ${report.dryRun}`\n );\n if (report.migratedProjects.length > 0) {\n for (const project of report.migratedProjects) {\n logger.info(\n `[nx-cucumber migration] ${project.project}: removed targets [${project.removedTargets.join(\n ', '\n )}], created cucumber.js: ${project.createdCucumberConfig}`\n );\n for (const followUp of project.manualFollowUps) {\n logger.warn(`[nx-cucumber migration] ${project.project}: ${followUp}`);\n }\n }\n }\n if (!dryRun) {\n logger.info(`[nx-cucumber migration] report written to ${REPORT_PATH}`);\n }\n\n return report;\n}\n\nexport default async function migrateCucumberApiToInferredTasksEntry(tree: Tree): Promise<void> {\n await migrateCucumberApiToInferredTasks(tree);\n}\n"]}
|
|
@@ -16,36 +16,71 @@ const debug = require('debug')('e2e:hooks');
|
|
|
16
16
|
if (!global.nxCucumber) {
|
|
17
17
|
global.nxCucumber = { disableCoverage: false };
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
const defaultCucumberTimeout = global.nxCucumber.timeout || 30_000;
|
|
20
|
+
const browserLaunchHookTimeout = Math.max(defaultCucumberTimeout, 60_000);
|
|
21
|
+
// Use an explicit timeout so hooks are not bound to Cucumber's 5s default on slower Windows runners.
|
|
20
22
|
if (process.env['PWDEBUG']) {
|
|
21
23
|
(0, cucumber_1.setDefaultTimeout)(-1);
|
|
22
24
|
}
|
|
25
|
+
else {
|
|
26
|
+
(0, cucumber_1.setDefaultTimeout)(defaultCucumberTimeout);
|
|
27
|
+
}
|
|
23
28
|
// We ignore all SSL errors, because we have self signed developer certificates
|
|
24
29
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
|
25
30
|
function configuredBrowser() {
|
|
26
31
|
return (0, playwright_browser_1.resolvePlaywrightBrowser)(global.nxCucumber.browser);
|
|
27
32
|
}
|
|
33
|
+
let browserLaunchPromise;
|
|
28
34
|
async function launchBrowser(label, headless) {
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (global.browserState.isStarted)
|
|
36
|
+
return;
|
|
37
|
+
if (!browserLaunchPromise) {
|
|
38
|
+
browserLaunchPromise = (async () => {
|
|
39
|
+
const browser = configuredBrowser();
|
|
40
|
+
debug(`${label} - Creating new ${headless ? 'headless' : 'headed'} ${browser} instance`);
|
|
41
|
+
const launchedBrowser = await (0, playwright_browser_2.resolvePlaywrightBrowserType)(browser).launch((0, playwright_browser_2.createPlaywrightLaunchOptions)(browser, headless));
|
|
42
|
+
if (global.browserState.isStarted) {
|
|
43
|
+
// A concurrent launch already produced a running browser (e.g. after a hook timeout).
|
|
44
|
+
// Close this duplicate instance so it cannot keep the process alive.
|
|
45
|
+
debug(`${label} - Closing duplicate ${browser} instance`);
|
|
46
|
+
try {
|
|
47
|
+
await launchedBrowser.close();
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
debug(`${label} - Could not close duplicate browser: %O`, e);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
global.browser = launchedBrowser;
|
|
55
|
+
global.browserState.isHeadless = headless;
|
|
56
|
+
global.browserState.isStarted = true;
|
|
57
|
+
debug(`${label} - ${browser} instance created`);
|
|
58
|
+
})().finally(() => {
|
|
59
|
+
browserLaunchPromise = undefined;
|
|
60
|
+
});
|
|
36
61
|
}
|
|
62
|
+
else {
|
|
63
|
+
debug(`${label} - Waiting for ongoing browser launch`);
|
|
64
|
+
}
|
|
65
|
+
await browserLaunchPromise;
|
|
37
66
|
}
|
|
38
67
|
async function closeBrowser(label) {
|
|
68
|
+
if (browserLaunchPromise) {
|
|
69
|
+
debug(`${label} - Waiting for ongoing browser launch before close`);
|
|
70
|
+
await browserLaunchPromise;
|
|
71
|
+
}
|
|
39
72
|
if (global.browserState.isStarted) {
|
|
40
73
|
debug(`${label} - Closing ${configuredBrowser()} instance`);
|
|
41
74
|
try {
|
|
42
75
|
await global.browser.close();
|
|
43
|
-
global.browserState.isStarted = false;
|
|
44
76
|
debug(`${label} - ${configuredBrowser()} instance closed`);
|
|
45
77
|
}
|
|
46
78
|
catch (e) {
|
|
47
79
|
debug(`${label} - Could not close browser: %O`, e);
|
|
48
80
|
}
|
|
81
|
+
finally {
|
|
82
|
+
global.browserState.isStarted = false;
|
|
83
|
+
}
|
|
49
84
|
}
|
|
50
85
|
}
|
|
51
86
|
/**
|
|
@@ -69,6 +104,7 @@ async function launchBrowserIfRequired(label, launchHeadless) {
|
|
|
69
104
|
}
|
|
70
105
|
}
|
|
71
106
|
(0, cucumber_1.BeforeAll)(async function () {
|
|
107
|
+
browserLaunchPromise = undefined;
|
|
72
108
|
global.browserState = {
|
|
73
109
|
isStarted: false,
|
|
74
110
|
isHeadless: true,
|
|
@@ -116,10 +152,10 @@ async function launchBrowserIfRequired(label, launchHeadless) {
|
|
|
116
152
|
// When javascript is enabled by default, we disable it only for scenarios which are tagged by @noscript
|
|
117
153
|
this.parameters.disableJavaScript = true;
|
|
118
154
|
});
|
|
119
|
-
(0, cucumber_1.Before)({ name: 'ensure headed browser', tags: '@headed' }, async function () {
|
|
155
|
+
(0, cucumber_1.Before)({ name: 'ensure headed browser', tags: '@headed', timeout: browserLaunchHookTimeout }, async function () {
|
|
120
156
|
await launchBrowserIfRequired('Before @headed', false);
|
|
121
157
|
});
|
|
122
|
-
(0, cucumber_1.Before)({ name: 'ensure headless browser', tags: 'not @headed' }, async function () {
|
|
158
|
+
(0, cucumber_1.Before)({ name: 'ensure headless browser', tags: 'not @headed', timeout: browserLaunchHookTimeout }, async function () {
|
|
123
159
|
await launchBrowserIfRequired('Before not @headed', !this.parameters.headed);
|
|
124
160
|
});
|
|
125
161
|
(0, cucumber_1.BeforeStep)(async function () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-hooks.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/playwright/playwright-hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, setDefaultTimeout } from '@cucumber/cucumber';\nimport { ITestCaseHookParameter } from '@cucumber/cucumber';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createIstanbulCoverageReports } from '@gnuechtel/shared-coverage';\nimport { PlaywrightWorld, scenarioName } from './playwright-world';\nimport { deleteExistingPlaywrightCoverageFiles } from '@gnuechtel/shared-coverage';\nimport { rootDirectory } from '@gnuechtel/shared-util';\nimport { resolvePlaywrightBrowser } from '../core/playwright-browser';\nimport { createPlaywrightLaunchOptions, resolvePlaywrightBrowserType } from './playwright-browser';\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst debug = require('debug')('e2e:hooks');\n\n// Initialize global configuration with defaults if not set (2.x: no executor injects this)\nif (!global.nxCucumber) {\n global.nxCucumber = { disableCoverage: false };\n}\n\n// Disable timeout on playwright debugging (let cucumber.js config handle the default otherwise)\nif (process.env['PWDEBUG']) {\n setDefaultTimeout(-1);\n}\n\n// We ignore all SSL errors, because we have self signed developer certificates\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';\n\nfunction configuredBrowser() {\n return resolvePlaywrightBrowser(global.nxCucumber.browser);\n}\n\nasync function launchBrowser(label: string, headless: boolean) {\n if (!global.browserState.isStarted) {\n const browser = configuredBrowser();\n debug(`${label} - Creating new ${headless ? 'headless' : 'headed'} ${browser} instance`);\n global.browser = await resolvePlaywrightBrowserType(browser).launch(\n createPlaywrightLaunchOptions(browser, headless)\n );\n global.browserState.isHeadless = headless;\n global.browserState.isStarted = true;\n debug(`${label} - ${browser} instance created`);\n }\n}\n\nasync function closeBrowser(label: string) {\n if (global.browserState.isStarted) {\n debug(`${label} - Closing ${configuredBrowser()} instance`);\n try {\n await global.browser.close();\n global.browserState.isStarted = false;\n debug(`${label} - ${configuredBrowser()} instance closed`);\n } catch (e) {\n debug(`${label} - Could not close browser: %O`, e);\n }\n }\n}\n\n/**\n * Launches the browser if it is not yet started or if the running headless state is different from `launchHeadless`.\n * Closes any existing browser before. This function has no effect, if a browser is currently running and has the same\n * headless state like `launchHeadless`.\n *\n * In other words, a new browser is launched\n * - headed, if running browser is headless\n * - headless, if running browser is headed\n * - headless/headed, if browser is not yet started.\n *\n * @param label the label for debugging\n * @param launchHeadless true, if browser should be launched headless, false otherwise\n */\nasync function launchBrowserIfRequired(label: string, launchHeadless: boolean) {\n if (global.browserState.isHeadless != launchHeadless || !global.browserState.isStarted) {\n // The closeBrowser invocation has only an effect, if browser is already running\n await closeBrowser(label);\n await launchBrowser(label, launchHeadless);\n }\n}\n\nBeforeAll(async function () {\n global.browserState = {\n isStarted: false,\n isHeadless: true,\n };\n if (!global.nxCucumber.disableCoverage) {\n debug('BeforeAll - Deleting existing coverage files');\n deleteExistingPlaywrightCoverageFiles();\n debug('BeforeAll - Existing coverage files deleted');\n }\n});\n\nAfterAll(async function () {\n if (!global.nxCucumber.disableCoverage) {\n const nycOutputDir = path.join(rootDirectory(), '.nyc_output');\n const hasCoverageFiles =\n fs.existsSync(nycOutputDir) &&\n fs.readdirSync(nycOutputDir).some((f) => f.startsWith('playwright_coverage_') && f.endsWith('.json'));\n if (hasCoverageFiles) {\n debug('AfterAll - Creating Istanbul coverage report');\n try {\n const projectDirectory = path.resolve(global.nxCucumber.cwd || process.cwd());\n await createIstanbulCoverageReports(rootDirectory(), projectDirectory);\n debug('AfterAll - Istanbul coverage report created');\n } catch (e) {\n debug('AfterAll - Could not create Istanbul coverage report: %O', e);\n }\n } else {\n debug('AfterAll - No coverage files found, skipping Istanbul coverage report');\n }\n }\n await closeBrowser('AfterAll');\n});\n\nBefore({ name: 'prepare' }, async function (this: PlaywrightWorld, testCase: ITestCaseHookParameter) {\n debug('Before - Cleaning up');\n // Always clean-up any running stuff\n await this.cleanUp();\n this.setItem(scenarioName, testCase.pickle.name); // save scenario name for screenshots or video files\n debug('Before - Cleaned up');\n});\n\nBefore({ name: 'enable javascript', tags: '@javascript' }, async function (this: PlaywrightWorld) {\n // When javascript is disabled by default, we enable it only for scenarios which are tagged by @javascript\n this.parameters.disableJavaScript = false;\n});\n\nBefore({ name: 'disable javascript', tags: '@noscript' }, async function (this: PlaywrightWorld) {\n // When javascript is enabled by default, we disable it only for scenarios which are tagged by @noscript\n this.parameters.disableJavaScript = true;\n});\n\nBefore({ name: 'ensure headed browser', tags: '@headed' }, async function () {\n await launchBrowserIfRequired('Before @headed', false);\n});\n\nBefore({ name: 'ensure headless browser', tags: 'not @headed' }, async function (this: PlaywrightWorld) {\n await launchBrowserIfRequired('Before not @headed', !this.parameters.headed);\n});\n\nBeforeStep(async function (this: PlaywrightWorld) {\n debug('BeforeStep');\n this.prepareStep();\n});\n\nAfterStep(async function (this: PlaywrightWorld, { result }) {\n if (!this.parameters.disableScreenshotAttachments || !this.parameters.disableScreenshotFiles) {\n try {\n debug('AfterStep - Creating screenshots');\n await this.createScreenshots(result);\n debug('AfterStep - Screenshots created');\n } catch (e) {\n debug('AfterStep - Could not create screenshots: %O', e);\n }\n }\n});\n\nAfter({ name: 'clean up' }, async function (this: PlaywrightWorld) {\n if (!this.parameters.disableCoverage) {\n // Wait for any pending network activity (e.g. lazy-loaded components) to settle before collecting coverage.\n try {\n for (const page of this.pages) {\n await page.waitForLoadState('networkidle', { timeout: 5000 });\n }\n } catch (_) {\n // best-effort — ignore timeouts\n }\n try {\n debug('After - Collecting coverage');\n await this.collectIstanbulCoverage();\n debug('After - Coverage collected');\n } catch (e) {\n debug('After - Could not create coverage: %O', e);\n }\n }\n try {\n debug('After - Cleaning up');\n await this.cleanUp();\n debug('After - Cleaned up');\n } catch (e) {\n debug('After - Could not clean up properly: %O', e);\n }\n});\n"]}
|
|
1
|
+
{"version":3,"file":"playwright-hooks.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/playwright/playwright-hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, setDefaultTimeout } from '@cucumber/cucumber';\nimport { ITestCaseHookParameter } from '@cucumber/cucumber';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createIstanbulCoverageReports } from '@gnuechtel/shared-coverage';\nimport { PlaywrightWorld, scenarioName } from './playwright-world';\nimport { deleteExistingPlaywrightCoverageFiles } from '@gnuechtel/shared-coverage';\nimport { rootDirectory } from '@gnuechtel/shared-util';\nimport { resolvePlaywrightBrowser } from '../core/playwright-browser';\nimport { createPlaywrightLaunchOptions, resolvePlaywrightBrowserType } from './playwright-browser';\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst debug = require('debug')('e2e:hooks');\n\n// Initialize global configuration with defaults if not set (2.x: no executor injects this)\nif (!global.nxCucumber) {\n global.nxCucumber = { disableCoverage: false };\n}\n\nconst defaultCucumberTimeout = global.nxCucumber.timeout || 30_000;\nconst browserLaunchHookTimeout = Math.max(defaultCucumberTimeout, 60_000);\n\n// Use an explicit timeout so hooks are not bound to Cucumber's 5s default on slower Windows runners.\nif (process.env['PWDEBUG']) {\n setDefaultTimeout(-1);\n} else {\n setDefaultTimeout(defaultCucumberTimeout);\n}\n\n// We ignore all SSL errors, because we have self signed developer certificates\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';\n\nfunction configuredBrowser() {\n return resolvePlaywrightBrowser(global.nxCucumber.browser);\n}\n\nlet browserLaunchPromise: Promise<void> | undefined;\n\nasync function launchBrowser(label: string, headless: boolean) {\n if (global.browserState.isStarted) return;\n\n if (!browserLaunchPromise) {\n browserLaunchPromise = (async () => {\n const browser = configuredBrowser();\n debug(`${label} - Creating new ${headless ? 'headless' : 'headed'} ${browser} instance`);\n const launchedBrowser = await resolvePlaywrightBrowserType(browser).launch(\n createPlaywrightLaunchOptions(browser, headless)\n );\n\n if (global.browserState.isStarted) {\n // A concurrent launch already produced a running browser (e.g. after a hook timeout).\n // Close this duplicate instance so it cannot keep the process alive.\n debug(`${label} - Closing duplicate ${browser} instance`);\n try {\n await launchedBrowser.close();\n } catch (e) {\n debug(`${label} - Could not close duplicate browser: %O`, e);\n }\n return;\n }\n\n global.browser = launchedBrowser;\n global.browserState.isHeadless = headless;\n global.browserState.isStarted = true;\n debug(`${label} - ${browser} instance created`);\n })().finally(() => {\n browserLaunchPromise = undefined;\n });\n } else {\n debug(`${label} - Waiting for ongoing browser launch`);\n }\n\n await browserLaunchPromise;\n}\n\nasync function closeBrowser(label: string) {\n if (browserLaunchPromise) {\n debug(`${label} - Waiting for ongoing browser launch before close`);\n await browserLaunchPromise;\n }\n\n if (global.browserState.isStarted) {\n debug(`${label} - Closing ${configuredBrowser()} instance`);\n try {\n await global.browser.close();\n debug(`${label} - ${configuredBrowser()} instance closed`);\n } catch (e) {\n debug(`${label} - Could not close browser: %O`, e);\n } finally {\n global.browserState.isStarted = false;\n }\n }\n}\n\n/**\n * Launches the browser if it is not yet started or if the running headless state is different from `launchHeadless`.\n * Closes any existing browser before. This function has no effect, if a browser is currently running and has the same\n * headless state like `launchHeadless`.\n *\n * In other words, a new browser is launched\n * - headed, if running browser is headless\n * - headless, if running browser is headed\n * - headless/headed, if browser is not yet started.\n *\n * @param label the label for debugging\n * @param launchHeadless true, if browser should be launched headless, false otherwise\n */\nasync function launchBrowserIfRequired(label: string, launchHeadless: boolean) {\n if (global.browserState.isHeadless != launchHeadless || !global.browserState.isStarted) {\n // The closeBrowser invocation has only an effect, if browser is already running\n await closeBrowser(label);\n await launchBrowser(label, launchHeadless);\n }\n}\n\nBeforeAll(async function () {\n browserLaunchPromise = undefined;\n global.browserState = {\n isStarted: false,\n isHeadless: true,\n };\n if (!global.nxCucumber.disableCoverage) {\n debug('BeforeAll - Deleting existing coverage files');\n deleteExistingPlaywrightCoverageFiles();\n debug('BeforeAll - Existing coverage files deleted');\n }\n});\n\nAfterAll(async function () {\n if (!global.nxCucumber.disableCoverage) {\n const nycOutputDir = path.join(rootDirectory(), '.nyc_output');\n const hasCoverageFiles =\n fs.existsSync(nycOutputDir) &&\n fs.readdirSync(nycOutputDir).some((f) => f.startsWith('playwright_coverage_') && f.endsWith('.json'));\n if (hasCoverageFiles) {\n debug('AfterAll - Creating Istanbul coverage report');\n try {\n const projectDirectory = path.resolve(global.nxCucumber.cwd || process.cwd());\n await createIstanbulCoverageReports(rootDirectory(), projectDirectory);\n debug('AfterAll - Istanbul coverage report created');\n } catch (e) {\n debug('AfterAll - Could not create Istanbul coverage report: %O', e);\n }\n } else {\n debug('AfterAll - No coverage files found, skipping Istanbul coverage report');\n }\n }\n await closeBrowser('AfterAll');\n});\n\nBefore({ name: 'prepare' }, async function (this: PlaywrightWorld, testCase: ITestCaseHookParameter) {\n debug('Before - Cleaning up');\n // Always clean-up any running stuff\n await this.cleanUp();\n this.setItem(scenarioName, testCase.pickle.name); // save scenario name for screenshots or video files\n debug('Before - Cleaned up');\n});\n\nBefore({ name: 'enable javascript', tags: '@javascript' }, async function (this: PlaywrightWorld) {\n // When javascript is disabled by default, we enable it only for scenarios which are tagged by @javascript\n this.parameters.disableJavaScript = false;\n});\n\nBefore({ name: 'disable javascript', tags: '@noscript' }, async function (this: PlaywrightWorld) {\n // When javascript is enabled by default, we disable it only for scenarios which are tagged by @noscript\n this.parameters.disableJavaScript = true;\n});\n\nBefore({ name: 'ensure headed browser', tags: '@headed', timeout: browserLaunchHookTimeout }, async function () {\n await launchBrowserIfRequired('Before @headed', false);\n});\n\nBefore(\n { name: 'ensure headless browser', tags: 'not @headed', timeout: browserLaunchHookTimeout },\n async function (this: PlaywrightWorld) {\n await launchBrowserIfRequired('Before not @headed', !this.parameters.headed);\n }\n);\n\nBeforeStep(async function (this: PlaywrightWorld) {\n debug('BeforeStep');\n this.prepareStep();\n});\n\nAfterStep(async function (this: PlaywrightWorld, { result }) {\n if (!this.parameters.disableScreenshotAttachments || !this.parameters.disableScreenshotFiles) {\n try {\n debug('AfterStep - Creating screenshots');\n await this.createScreenshots(result);\n debug('AfterStep - Screenshots created');\n } catch (e) {\n debug('AfterStep - Could not create screenshots: %O', e);\n }\n }\n});\n\nAfter({ name: 'clean up' }, async function (this: PlaywrightWorld) {\n if (!this.parameters.disableCoverage) {\n // Wait for any pending network activity (e.g. lazy-loaded components) to settle before collecting coverage.\n try {\n for (const page of this.pages) {\n await page.waitForLoadState('networkidle', { timeout: 5000 });\n }\n } catch (_) {\n // best-effort — ignore timeouts\n }\n try {\n debug('After - Collecting coverage');\n await this.collectIstanbulCoverage();\n debug('After - Coverage collected');\n } catch (e) {\n debug('After - Could not create coverage: %O', e);\n }\n }\n try {\n debug('After - Cleaning up');\n await this.cleanUp();\n debug('After - Cleaned up');\n } catch (e) {\n debug('After - Could not clean up properly: %O', e);\n }\n});\n"]}
|