@wdio/nightwatch-devtools 1.6.0 → 1.7.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 CHANGED
@@ -166,6 +166,8 @@ For the full artifact contents, the `traceGranularity` / `tracePolicy` reference
166
166
 
167
167
  The per-test options — `traceGranularity: 'test'`, and the `tracePolicy`, `screenshot`, and `video` options that pair with it — need a per-test hook to cut each test's slice. The **exports-object (mocha-style)** interface and **Cucumber** (per-scenario hooks) expose one, so they get real per-test slicing. The **BDD `describe/it`** interface — the style used by this package's bundled example — is the exception: Nightwatch runs each `it()` internally and fires the plugin's per-test hook only once per module, so `traceGranularity: 'test'` collapses to a single **session-scoped** slice keyed to the first test. The artifacts manifest still lists every testcase with its correct state; only the per-test slice/artifact keying collapses. Session- and spec-granularity traces are unaffected.
168
168
 
169
+ **Cucumber and `traceGranularity`.** Nightwatch quits the browser after every scenario. A `'session'` trace now spans that — one zip covering the whole run, every scenario, with each scenario nested under its feature. (Before, it captured only the *last* scenario, silently.) `'test'` remains the recommendation for Cucumber: one zip per scenario, in its own folder, keyed for retention and smaller to open.
170
+
169
171
  ---
170
172
 
171
173
  ## BiDi capture
@@ -237,7 +239,7 @@ The encoded video is sent to the DevTools dashboard via the `screencast` WS scop
237
239
  | `bidi` | `boolean` | `false` | Opt into WebDriver BiDi capture for browser console + JS exceptions + network. Requires `webSocketUrl: true` in your capabilities and a BiDi-capable chromedriver. When attached, the per-command Chrome perf-log network path is gated off so requests don't duplicate. |
238
240
  | `mode` | `'live' \| 'trace'` | `'live'` | `'live'` opens the DevTools UI window; `'trace'` skips the UI and writes a `trace-<sessionId>.zip` (or directory) under a `test-results/` directory at run end (base dir resolved from the test file dir → config dir → cwd). See [Trace mode](#trace-mode). |
239
241
  | `traceFormat` | `'zip' \| 'ndjson-directory'` | `'zip'` | Trace artifact layout — `zip` writes a single archive, `ndjson-directory` unpacks the same files into `trace-<sessionId>/`. Only applies when `mode: 'trace'`. |
240
- | `traceGranularity` | `'session' \| 'spec' \| 'test'` | `'session'` | One trace per session / spec file / test. `'test'` is what the per-test `screenshot` / `video` artifacts attach to. Only applies when `mode: 'trace'`. **Caveat:** the BDD `describe/it` interface collapses to a single session-scoped slice (see [Per-test slicing](#per-test-slicing--the-bdd-describeit-caveat)). |
242
+ | `traceGranularity` | `'session' \| 'spec' \| 'test'` | `'session'` | One trace per session / spec file / test. `'test'` is what the per-test `screenshot` / `video` artifacts attach to. Only applies when `mode: 'trace'`. **Caveat:** the BDD `describe/it` interface collapses to a single session-scoped slice (see [Per-test slicing](#per-test-slicing--the-bdd-describeit-caveat)). For Cucumber, `'session'` spans every scenario in one zip; `'test'` gives one per scenario and is the recommendation. |
241
243
  | `tracePolicy` | `'on' \| 'retain-on-failure' \| 'retain-on-first-failure' \| 'on-first-retry' \| 'on-all-retries' \| 'retain-on-failure-and-retries'` | `'on'` | Which traces to keep. Pairs with `traceGranularity: 'test'`. Only applies when `mode: 'trace'`. |
242
244
  | `filmstrip` | `boolean` | `true` | Record a dense, continuous screencast filmstrip into the trace for scrubbable playback in the trace player — not just one frame per action. Runs the screencast recorder (polling mode on Nightwatch) for the session. Only applies when `mode: 'trace'`. |
243
245
  | `screenshot` | `'off' \| 'on' \| 'only-on-failure'` | `'off'` | Per-test screenshot. Trace mode + `traceGranularity: 'test'` only. **Produce-only** — the PNG is written to the trace output dir (and listed in the artifacts manifest when `emitArtifactsManifest: true`); it is NOT attached inline to Allure (see note below). |
@@ -0,0 +1,42 @@
1
+ import {
2
+ attachBidiHandlers
3
+ } from "./chunk-QYGJTYKJ.js";
4
+
5
+ // src/bidi.ts
6
+ import logger from "@wdio/logger";
7
+ var log = logger("@wdio/nightwatch-devtools:bidi");
8
+ async function attachBidiHandlers2(driver, sinks) {
9
+ return attachBidiHandlers(
10
+ driver,
11
+ sinks,
12
+ (level, message) => log[level](message)
13
+ );
14
+ }
15
+ function buildBidiSinks(capturer) {
16
+ return {
17
+ pushConsoleLog: (entry) => {
18
+ capturer.consoleLogs.push(entry);
19
+ capturer.sendUpstream("consoleLogs", [entry]);
20
+ },
21
+ pushNetworkRequest: (entry) => {
22
+ capturer.networkRequests.push(entry);
23
+ capturer.sendUpstream("networkRequests", [entry]);
24
+ },
25
+ replaceNetworkRequest: (id, entry) => {
26
+ const idx = capturer.networkRequests.findIndex(
27
+ (r) => r.id === id
28
+ );
29
+ if (idx !== -1) {
30
+ capturer.networkRequests[idx] = entry;
31
+ } else {
32
+ capturer.networkRequests.push(entry);
33
+ }
34
+ capturer.sendUpstream("networkRequests", [entry]);
35
+ }
36
+ };
37
+ }
38
+ export {
39
+ attachBidiHandlers2 as attachBidiHandlers,
40
+ buildBidiSinks
41
+ };
42
+ //# sourceMappingURL=bidi-4GYMJUJN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bidi.ts"],"sourcesContent":["import logger from '@wdio/logger'\nimport {\n type BidiHandlerSinks,\n attachBidiHandlers as attachBidiHandlersCore\n} from '@wdio/devtools-core'\nimport type { SessionCapturer } from './session.js'\n\nconst log = logger('@wdio/nightwatch-devtools:bidi')\n\n/**\n * Nightwatch wrapper around the core BiDi attach helper. Nightwatch ships\n * selenium-webdriver under the hood (via chromedriver), so the same\n * `selenium-webdriver/bidi` inspectors selenium-devtools uses are available\n * whenever the user has set `webSocketUrl: true` in their capabilities.\n *\n * Opt-in via the plugin's `bidi: true` option. When attached, the per-command\n * Chrome performance-log network capture is gated off to avoid duplicate\n * entries in the dashboard.\n */\nexport async function attachBidiHandlers(\n driver: unknown,\n sinks: BidiHandlerSinks\n): Promise<boolean> {\n return attachBidiHandlersCore(driver, sinks, (level, message) =>\n log[level](message)\n )\n}\n\n/**\n * Build sinks that route BiDi events into the SessionCapturer's local arrays\n * and broadcast them upstream. Mirrors selenium-devtools' `buildBidiSinks` —\n * separate per-adapter because the SessionCapturer concrete types differ.\n */\nexport function buildBidiSinks(capturer: SessionCapturer): BidiHandlerSinks {\n return {\n pushConsoleLog: (entry) => {\n capturer.consoleLogs.push(entry)\n capturer.sendUpstream('consoleLogs', [entry])\n },\n pushNetworkRequest: (entry) => {\n capturer.networkRequests.push(entry)\n capturer.sendUpstream('networkRequests', [entry])\n },\n replaceNetworkRequest: (id, entry) => {\n const idx = capturer.networkRequests.findIndex(\n (r: { id?: string }) => r.id === id\n )\n if (idx !== -1) {\n capturer.networkRequests[idx] = entry\n } else {\n capturer.networkRequests.push(entry)\n }\n capturer.sendUpstream('networkRequests', [entry])\n }\n }\n}\n"],"mappings":";;;;;AAAA,OAAO,YAAY;AAOnB,IAAM,MAAM,OAAO,gCAAgC;AAYnD,eAAsBA,oBACpB,QACA,OACkB;AAClB,SAAO;AAAA,IAAuB;AAAA,IAAQ;AAAA,IAAO,CAAC,OAAO,YACnD,IAAI,KAAK,EAAE,OAAO;AAAA,EACpB;AACF;AAOO,SAAS,eAAe,UAA6C;AAC1E,SAAO;AAAA,IACL,gBAAgB,CAAC,UAAU;AACzB,eAAS,YAAY,KAAK,KAAK;AAC/B,eAAS,aAAa,eAAe,CAAC,KAAK,CAAC;AAAA,IAC9C;AAAA,IACA,oBAAoB,CAAC,UAAU;AAC7B,eAAS,gBAAgB,KAAK,KAAK;AACnC,eAAS,aAAa,mBAAmB,CAAC,KAAK,CAAC;AAAA,IAClD;AAAA,IACA,uBAAuB,CAAC,IAAI,UAAU;AACpC,YAAM,MAAM,SAAS,gBAAgB;AAAA,QACnC,CAAC,MAAuB,EAAE,OAAO;AAAA,MACnC;AACA,UAAI,QAAQ,IAAI;AACd,iBAAS,gBAAgB,GAAG,IAAI;AAAA,MAClC,OAAO;AACL,iBAAS,gBAAgB,KAAK,KAAK;AAAA,MACrC;AACA,eAAS,aAAa,mBAAmB,CAAC,KAAK,CAAC;AAAA,IAClD;AAAA,EACF;AACF;","names":["attachBidiHandlers"]}
@@ -0,0 +1,380 @@
1
+ // ../core/src/error.ts
2
+ function toError(value) {
3
+ if (value instanceof Error) {
4
+ return value;
5
+ }
6
+ if (value !== null && typeof value === "object" && typeof value.message === "string") {
7
+ const e = new Error(value.message);
8
+ const name = value.name;
9
+ if (typeof name === "string") {
10
+ e.name = name;
11
+ }
12
+ return e;
13
+ }
14
+ return new Error(String(value));
15
+ }
16
+ function errorMessage(value) {
17
+ if (value instanceof Error) {
18
+ return value.message;
19
+ }
20
+ if (typeof value === "string") {
21
+ return value;
22
+ }
23
+ if (value !== null && typeof value === "object" && typeof value.message === "string") {
24
+ return value.message;
25
+ }
26
+ if (value === void 0 || value === null) {
27
+ return "unknown error";
28
+ }
29
+ try {
30
+ return String(value);
31
+ } catch {
32
+ return "unknown error";
33
+ }
34
+ }
35
+ function serializeError(error) {
36
+ if (!error) {
37
+ return void 0;
38
+ }
39
+ return { name: error.name, message: error.message, stack: error.stack };
40
+ }
41
+
42
+ // ../core/src/bidi.ts
43
+ import { createRequire } from "module";
44
+
45
+ // ../core/src/console.ts
46
+ var CONSOLE_METHODS = ["log", "info", "warn", "error"];
47
+ var ANSI_REGEX = /\x1b\[[?]?[0-9;]*[A-Za-z]/g;
48
+ function stripAnsi(text) {
49
+ return text.replace(ANSI_REGEX, "");
50
+ }
51
+ var LOG_LEVEL_PATTERNS = [
52
+ { level: "trace", pattern: /\btrace\b/i },
53
+ { level: "debug", pattern: /\bdebug\b/i },
54
+ { level: "info", pattern: /\binfo\b/i },
55
+ { level: "warn", pattern: /\bwarn(ing)?\b/i },
56
+ { level: "error", pattern: /\berror\b/i }
57
+ ];
58
+ var ERROR_INDICATORS = ["\u2717", "failed", "failure"];
59
+ var SPINNER_RE = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/u;
60
+ function isInternalStreamLine(line) {
61
+ const t = line.trim();
62
+ if (t.startsWith('{"') || t.startsWith("[SESSION]")) {
63
+ return true;
64
+ }
65
+ if (t.includes("@wdio/devtools-backend")) {
66
+ return true;
67
+ }
68
+ if (/^console\.(log|info|warn|error|debug|trace)$/.test(t)) {
69
+ return true;
70
+ }
71
+ if (/^at\s.+:\d+:\d+\)?$/.test(t)) {
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ var LOG_SOURCES = {
77
+ BROWSER: "browser",
78
+ TEST: "test",
79
+ TERMINAL: "terminal"
80
+ };
81
+ function detectLogLevel(text) {
82
+ const normalised = stripAnsi(text).toLowerCase();
83
+ for (const { level, pattern } of LOG_LEVEL_PATTERNS) {
84
+ if (pattern.test(normalised)) {
85
+ return level;
86
+ }
87
+ }
88
+ if (ERROR_INDICATORS.some((i) => normalised.includes(i.toLowerCase()))) {
89
+ return "error";
90
+ }
91
+ return "log";
92
+ }
93
+ function createConsoleLogEntry(type, args, source = LOG_SOURCES.TEST) {
94
+ return { timestamp: Date.now(), type, args, source };
95
+ }
96
+ function mapChromeBrowserLogs(entries) {
97
+ return entries.map((entry) => ({
98
+ timestamp: entry.timestamp,
99
+ type: chromeLogLevelToLogLevel(
100
+ entry.level
101
+ ),
102
+ args: [entry.message],
103
+ source: LOG_SOURCES.BROWSER
104
+ }));
105
+ }
106
+ function chromeLogLevelToLogLevel(level) {
107
+ const levelName = (typeof level === "object" ? level?.name ?? "" : level ?? "").toUpperCase();
108
+ switch (levelName) {
109
+ case "SEVERE":
110
+ return "error";
111
+ case "WARNING":
112
+ return "warn";
113
+ case "INFO":
114
+ return "info";
115
+ case "DEBUG":
116
+ return "debug";
117
+ default:
118
+ return "log";
119
+ }
120
+ }
121
+
122
+ // ../core/src/net.ts
123
+ import * as net from "net";
124
+
125
+ // ../core/src/request-type.ts
126
+ function getRequestType(url, mimeType) {
127
+ const contentType = mimeType?.toLowerCase() ?? "";
128
+ const urlLower = url.toLowerCase();
129
+ if (contentType.includes("text/html")) {
130
+ return "document";
131
+ }
132
+ if (contentType.includes("text/css")) {
133
+ return "stylesheet";
134
+ }
135
+ if (contentType.includes("javascript") || contentType.includes("ecmascript")) {
136
+ return "script";
137
+ }
138
+ if (contentType.includes("image/")) {
139
+ return "image";
140
+ }
141
+ if (contentType.includes("font/") || contentType.includes("woff")) {
142
+ return "font";
143
+ }
144
+ if (contentType.includes("application/json")) {
145
+ return "fetch";
146
+ }
147
+ if (urlLower.endsWith(".html") || urlLower.endsWith(".htm")) {
148
+ return "document";
149
+ }
150
+ if (urlLower.endsWith(".css")) {
151
+ return "stylesheet";
152
+ }
153
+ if (urlLower.endsWith(".js") || urlLower.endsWith(".mjs")) {
154
+ return "script";
155
+ }
156
+ if (/\.(png|jpg|jpeg|gif|svg|webp|ico)$/.test(urlLower)) {
157
+ return "image";
158
+ }
159
+ if (/\.(woff|woff2|ttf|eot|otf)$/.test(urlLower)) {
160
+ return "font";
161
+ }
162
+ return "xhr";
163
+ }
164
+
165
+ // ../core/src/net.ts
166
+ function isPortInUse(port, hostname) {
167
+ return new Promise((resolve) => {
168
+ const server = net.createServer();
169
+ server.once("error", () => resolve(true));
170
+ server.once("listening", () => server.close(() => resolve(false)));
171
+ server.listen(port, hostname);
172
+ });
173
+ }
174
+ async function findFreePort(startPort, hostname) {
175
+ let port = startPort;
176
+ while (await isPortInUse(port, hostname)) {
177
+ port++;
178
+ }
179
+ return port;
180
+ }
181
+
182
+ // ../core/src/bidi.ts
183
+ function loadSeleniumSubmodule(subpath) {
184
+ try {
185
+ const userRequire = createRequire(`${process.cwd()}/`);
186
+ return userRequire(`selenium-webdriver/${subpath}`);
187
+ } catch {
188
+ try {
189
+ const localRequire = createRequire(import.meta.url);
190
+ return localRequire(`selenium-webdriver/${subpath}`);
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+ }
196
+ function handleBidiConsoleEntry(rawEntry, sinks, log) {
197
+ const entry = rawEntry;
198
+ try {
199
+ const level = (entry?.level ?? entry?.type ?? "info").toString();
200
+ const text = entry?.text ?? entry?.message ?? "";
201
+ sinks.pushConsoleLog({
202
+ timestamp: Number(entry?.timestamp) || Date.now(),
203
+ type: chromeLogLevelToLogLevel(level),
204
+ args: [text],
205
+ source: LOG_SOURCES.BROWSER
206
+ });
207
+ } catch (err) {
208
+ log("warn", `onConsoleEntry handler threw: ${errorMessage(err)}`);
209
+ }
210
+ }
211
+ function handleBidiJsException(rawExc, sinks, log) {
212
+ const exception = rawExc;
213
+ try {
214
+ const text = exception?.text ?? exception?.message ?? String(rawExc);
215
+ const trimmed = String(text).replace(/\s+/g, " ").slice(0, 200);
216
+ log(
217
+ "warn",
218
+ `\u{1F41B} JS error in page: ${trimmed}${String(text).length > 200 ? "\u2026" : ""}`
219
+ );
220
+ sinks.pushConsoleLog({
221
+ timestamp: Date.now(),
222
+ type: "error",
223
+ args: [text],
224
+ source: LOG_SOURCES.BROWSER
225
+ });
226
+ } catch (err) {
227
+ log("warn", `onJavascriptException handler threw: ${errorMessage(err)}`);
228
+ }
229
+ }
230
+ async function attachLogInspector(driver, factory, sinks, log) {
231
+ try {
232
+ const inspector = await factory(driver);
233
+ await inspector.onConsoleEntry((e) => handleBidiConsoleEntry(e, sinks, log));
234
+ await inspector.onJavascriptException(
235
+ (e) => handleBidiJsException(e, sinks, log)
236
+ );
237
+ log("info", "\u2713 BiDi LogInspector attached (console + JS exceptions)");
238
+ return true;
239
+ } catch (err) {
240
+ log("warn", `BiDi LogInspector attach failed: ${errorMessage(err)}`);
241
+ return false;
242
+ }
243
+ }
244
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
245
+ function bidiResponseTiming(timings, startTime, timestamp) {
246
+ if (timings && isFiniteNumber(timings.requestTime) && isFiniteNumber(timings.responseEnd) && timings.responseEnd > timings.requestTime) {
247
+ const time = Math.round(timings.responseEnd - timings.requestTime);
248
+ return { endTime: startTime + time, time };
249
+ }
250
+ const endTime = Number(timestamp ?? Date.now());
251
+ return { endTime, time: Math.max(0, endTime - startTime) };
252
+ }
253
+ function handleBidiRequestSent(rawEvent, pending, sinks, log) {
254
+ const event = rawEvent;
255
+ try {
256
+ const requestId = String(event?.request?.request ?? event?.id ?? "");
257
+ if (!requestId) {
258
+ return;
259
+ }
260
+ const entry = {
261
+ id: requestId,
262
+ url: event?.request?.url ?? "",
263
+ method: event?.request?.method ?? "GET",
264
+ requestHeaders: arrayHeadersToObject(event?.request?.headers),
265
+ timestamp: Date.now(),
266
+ startTime: Number(event?.timestamp ?? Date.now()),
267
+ type: getRequestType(event?.request?.url ?? "")
268
+ };
269
+ pending.set(requestId, entry);
270
+ sinks.pushNetworkRequest(entry);
271
+ } catch (err) {
272
+ log("warn", `beforeRequestSent threw: ${errorMessage(err)}`);
273
+ }
274
+ }
275
+ function handleBidiResponseCompleted(rawEvent, pending, sinks, log) {
276
+ const event = rawEvent;
277
+ try {
278
+ const requestId = String(event?.request?.request ?? event?.id ?? "");
279
+ const previous = pending.get(requestId);
280
+ if (!previous) {
281
+ return;
282
+ }
283
+ const { endTime, time } = bidiResponseTiming(
284
+ event?.request?.timings,
285
+ previous.startTime,
286
+ event?.timestamp
287
+ );
288
+ const finalized = {
289
+ ...previous,
290
+ status: Number(event?.response?.status) || previous.status,
291
+ statusText: event?.response?.statusText ?? previous.statusText,
292
+ responseHeaders: arrayHeadersToObject(event?.response?.headers),
293
+ type: getRequestType(previous.url, event?.response?.mimeType),
294
+ endTime,
295
+ time,
296
+ size: Number(event?.response?.bytesReceived) || void 0
297
+ };
298
+ pending.delete(requestId);
299
+ sinks.replaceNetworkRequest(requestId, finalized);
300
+ } catch (err) {
301
+ log("warn", `responseCompleted threw: ${errorMessage(err)}`);
302
+ }
303
+ }
304
+ async function attachNetworkInspector(driver, factory, sinks, log) {
305
+ try {
306
+ const inspector = await factory(driver);
307
+ const pending = /* @__PURE__ */ new Map();
308
+ await inspector.beforeRequestSent(
309
+ (e) => handleBidiRequestSent(e, pending, sinks, log)
310
+ );
311
+ await inspector.responseCompleted(
312
+ (e) => handleBidiResponseCompleted(e, pending, sinks, log)
313
+ );
314
+ log("info", "\u2713 BiDi NetworkInspector attached (request + response)");
315
+ return true;
316
+ } catch (err) {
317
+ log("warn", `BiDi NetworkInspector attach failed: ${errorMessage(err)}`);
318
+ return false;
319
+ }
320
+ }
321
+ async function attachBidiHandlers(driver, sinks, onLog) {
322
+ const log = (level, message) => onLog?.(level, message);
323
+ const logFactory = loadSeleniumSubmodule("bidi/logInspector");
324
+ const networkFactory = loadSeleniumSubmodule(
325
+ "bidi/networkInspector"
326
+ );
327
+ let attached = 0;
328
+ if (typeof logFactory === "function") {
329
+ if (await attachLogInspector(driver, logFactory, sinks, log)) {
330
+ attached++;
331
+ }
332
+ } else {
333
+ log("info", "selenium-webdriver/bidi/logInspector not available \u2014 skipping");
334
+ }
335
+ if (typeof networkFactory === "function") {
336
+ if (await attachNetworkInspector(driver, networkFactory, sinks, log)) {
337
+ attached++;
338
+ }
339
+ } else {
340
+ log(
341
+ "info",
342
+ "selenium-webdriver/bidi/networkInspector not available \u2014 skipping"
343
+ );
344
+ }
345
+ return attached > 0;
346
+ }
347
+ function arrayHeadersToObject(headers) {
348
+ if (!Array.isArray(headers)) {
349
+ return void 0;
350
+ }
351
+ const out = {};
352
+ for (const h of headers) {
353
+ const name = String(h?.name ?? "").toLowerCase();
354
+ if (!name) {
355
+ continue;
356
+ }
357
+ const v = h?.value;
358
+ out[name] = typeof v === "string" ? v : typeof v?.value === "string" ? v.value : JSON.stringify(v ?? "");
359
+ }
360
+ return out;
361
+ }
362
+
363
+ export {
364
+ toError,
365
+ errorMessage,
366
+ serializeError,
367
+ CONSOLE_METHODS,
368
+ stripAnsi,
369
+ SPINNER_RE,
370
+ isInternalStreamLine,
371
+ LOG_SOURCES,
372
+ detectLogLevel,
373
+ createConsoleLogEntry,
374
+ mapChromeBrowserLogs,
375
+ getRequestType,
376
+ findFreePort,
377
+ loadSeleniumSubmodule,
378
+ attachBidiHandlers
379
+ };
380
+ //# sourceMappingURL=chunk-QYGJTYKJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/error.ts","../../core/src/bidi.ts","../../core/src/console.ts","../../core/src/net.ts","../../core/src/request-type.ts"],"sourcesContent":["/** Plain-object shape of an Error after `serializeError`. */\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\n/**\n * Coerce an unknown value (caught exception, framework-supplied error\n * object, string, etc.) into an Error instance. Used at adapter command\n * boundaries where caught values can be anything — Error subclasses,\n * thrown strings, framework objects with a `.message` — and downstream\n * code wants a stable `Error` to inspect and serialize.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { message?: unknown }).message === 'string'\n ) {\n const e = new Error((value as { message: string }).message)\n const name = (value as { name?: unknown }).name\n if (typeof name === 'string') {\n e.name = name\n }\n return e\n }\n return new Error(String(value))\n}\n\n/**\n * Extract a printable message from a caught value. Equivalent to reading\n * `.message` on an Error, but degrades cleanly when the thrown value is a\n * string, a plain object, undefined, or anything else — `(err as Error).message`\n * silently returns `undefined` in those cases and yields useless log output.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message\n }\n if (typeof value === 'string') {\n return value\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { message?: unknown }).message === 'string'\n ) {\n return (value as { message: string }).message\n }\n if (value === undefined || value === null) {\n return 'unknown error'\n }\n try {\n return String(value)\n } catch {\n return 'unknown error'\n }\n}\n\n/**\n * True when an error message signals the WebDriver session is gone or\n * unreachable — expected when deferred/async capture work races session\n * teardown. Adapters use it to swallow post-quit errors instead of logging noise.\n */\nexport function isSessionGoneError(message: string): boolean {\n return (\n message.includes('ECONNREFUSED') ||\n message.includes('no such session') ||\n message.includes('invalid session id')\n )\n}\n\n/**\n * Normalize an Error to a plain object so its fields survive `JSON.stringify`\n * over the WS bridge. Error instances have `message`/`name`/`stack` as\n * non-enumerable, which `JSON.stringify` would drop.\n *\n * Returns `undefined` when the input is undefined so callers can pass through\n * possibly-undefined values without an extra branch.\n */\nexport function serializeError(\n error: Error | undefined\n): SerializedError | undefined {\n if (!error) {\n return undefined\n }\n return { name: error.name, message: error.message, stack: error.stack }\n}\n","import { createRequire } from 'node:module'\nimport type {\n ConsoleLog,\n LogLevel,\n NetworkRequest\n} from '@wdio/devtools-shared'\nimport {\n LOG_SOURCES,\n chromeLogLevelToLogLevel,\n type LogSource\n} from './console.js'\nimport { errorMessage } from './error.js'\nimport { getRequestType } from './net.js'\n\n/**\n * Generic sinks the BiDi handlers push into. Each adapter wires these to its\n * own SessionCapturer state — selenium's `buildBidiSinks` is the canonical\n * example; nightwatch can mirror the pattern when it wires up BiDi.\n */\nexport interface BidiHandlerSinks {\n pushConsoleLog: (entry: ConsoleLog) => void\n pushNetworkRequest: (entry: NetworkRequest) => void\n replaceNetworkRequest: (id: string, entry: NetworkRequest) => void\n}\n\n/**\n * Resolve a `selenium-webdriver/<subpath>` module from the user's install\n * (preferred) or the package's local install (fallback). Returns `null` if\n * neither resolves — caller should treat as \"BiDi not available on this\n * runtime\" and degrade gracefully.\n *\n * Used by both selenium-devtools and (when wired up) nightwatch-devtools —\n * both ship selenium-webdriver-style drivers under the hood.\n */\nexport function loadSeleniumSubmodule<T = unknown>(subpath: string): T | null {\n try {\n const userRequire = createRequire(`${process.cwd()}/`)\n return userRequire(`selenium-webdriver/${subpath}`) as T\n } catch {\n try {\n const localRequire = createRequire(import.meta.url)\n return localRequire(`selenium-webdriver/${subpath}`) as T\n } catch {\n return null\n }\n }\n}\n\ntype BidiLogger = (level: 'info' | 'warn', message: string) => void\ntype InspectorFactory = (driver: unknown) => Promise<unknown>\n\ninterface LogInspector {\n onConsoleEntry: (cb: (entry: unknown) => void) => Promise<void>\n onJavascriptException: (cb: (exc: unknown) => void) => Promise<void>\n}\n\ninterface NetworkInspector {\n beforeRequestSent: (cb: (e: unknown) => void) => Promise<void>\n responseCompleted: (cb: (e: unknown) => void) => Promise<void>\n}\n\nexport function handleBidiConsoleEntry(\n rawEntry: unknown,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const entry = rawEntry as {\n level?: string\n type?: string\n text?: string\n message?: string\n timestamp?: number\n }\n try {\n const level = (entry?.level ?? entry?.type ?? 'info').toString()\n const text = entry?.text ?? entry?.message ?? ''\n sinks.pushConsoleLog({\n timestamp: Number(entry?.timestamp) || Date.now(),\n type: chromeLogLevelToLogLevel(level) as LogLevel,\n args: [text],\n source: LOG_SOURCES.BROWSER as LogSource\n })\n } catch (err) {\n log('warn', `onConsoleEntry handler threw: ${errorMessage(err)}`)\n }\n}\n\nexport function handleBidiJsException(\n rawExc: unknown,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const exception = rawExc as { text?: string; message?: string }\n try {\n const text = exception?.text ?? exception?.message ?? String(rawExc)\n const trimmed = String(text).replace(/\\s+/g, ' ').slice(0, 200)\n log(\n 'warn',\n `🐛 JS error in page: ${trimmed}${String(text).length > 200 ? '…' : ''}`\n )\n sinks.pushConsoleLog({\n timestamp: Date.now(),\n type: 'error',\n args: [text],\n source: LOG_SOURCES.BROWSER as LogSource\n })\n } catch (err) {\n log('warn', `onJavascriptException handler threw: ${errorMessage(err)}`)\n }\n}\n\nasync function attachLogInspector(\n driver: unknown,\n factory: InspectorFactory,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): Promise<boolean> {\n try {\n const inspector = (await factory(driver)) as LogInspector\n await inspector.onConsoleEntry((e) => handleBidiConsoleEntry(e, sinks, log))\n await inspector.onJavascriptException((e) =>\n handleBidiJsException(e, sinks, log)\n )\n log('info', '✓ BiDi LogInspector attached (console + JS exceptions)')\n return true\n } catch (err) {\n log('warn', `BiDi LogInspector attach failed: ${errorMessage(err)}`)\n return false\n }\n}\n\n/** Subset of WebDriver BiDi `FetchTimingInfo` (selenium exposes these via\n * getters). Values are ms offsets from the request baseline `requestTime`\n * (usually 0), so `responseEnd - requestTime` is the request duration. */\ninterface FetchTimings {\n requestTime?: number\n responseEnd?: number\n}\n\nconst isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\ninterface BeforeRequestSentEvent {\n request?: {\n request?: string\n url?: string\n method?: string\n headers?: unknown\n timings?: FetchTimings\n }\n id?: string\n timestamp?: number\n}\n\ninterface ResponseCompletedEvent {\n request?: { request?: string; timings?: FetchTimings }\n id?: string\n timestamp?: number\n response?: {\n status?: number\n statusText?: string\n headers?: unknown\n mimeType?: string\n bytesReceived?: number\n }\n}\n\n/** Request duration (ms) from the browser's FetchTimingInfo, plus a derived\n * endTime anchored to `startTime`. The browser timings are immune to BiDi\n * events arriving batched in one tick (which collapses the event-level\n * `timestamp`/`Date.now()` to a single value and yields 0-duration requests).\n * Falls back to the timestamp diff only when timings are unavailable. */\nfunction bidiResponseTiming(\n timings: FetchTimings | undefined,\n startTime: number,\n timestamp?: number\n): { endTime: number; time: number } {\n if (\n timings &&\n isFiniteNumber(timings.requestTime) &&\n isFiniteNumber(timings.responseEnd) &&\n timings.responseEnd > timings.requestTime\n ) {\n const time = Math.round(timings.responseEnd - timings.requestTime)\n return { endTime: startTime + time, time }\n }\n const endTime = Number(timestamp ?? Date.now())\n return { endTime, time: Math.max(0, endTime - startTime) }\n}\n\nexport function handleBidiRequestSent(\n rawEvent: unknown,\n pending: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const event = rawEvent as BeforeRequestSentEvent\n try {\n const requestId = String(event?.request?.request ?? event?.id ?? '')\n if (!requestId) {\n return\n }\n const entry: NetworkRequest = {\n id: requestId,\n url: event?.request?.url ?? '',\n method: event?.request?.method ?? 'GET',\n requestHeaders: arrayHeadersToObject(event?.request?.headers),\n timestamp: Date.now(),\n startTime: Number(event?.timestamp ?? Date.now()),\n type: getRequestType(event?.request?.url ?? '')\n }\n pending.set(requestId, entry)\n sinks.pushNetworkRequest(entry)\n } catch (err) {\n log('warn', `beforeRequestSent threw: ${errorMessage(err)}`)\n }\n}\n\nexport function handleBidiResponseCompleted(\n rawEvent: unknown,\n pending: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const event = rawEvent as ResponseCompletedEvent\n try {\n const requestId = String(event?.request?.request ?? event?.id ?? '')\n const previous = pending.get(requestId)\n if (!previous) {\n return\n }\n const { endTime, time } = bidiResponseTiming(\n event?.request?.timings,\n previous.startTime,\n event?.timestamp\n )\n const finalized: NetworkRequest = {\n ...previous,\n status: Number(event?.response?.status) || previous.status,\n statusText: event?.response?.statusText ?? previous.statusText,\n responseHeaders: arrayHeadersToObject(event?.response?.headers),\n type: getRequestType(previous.url, event?.response?.mimeType),\n endTime,\n time,\n size: Number(event?.response?.bytesReceived) || undefined\n }\n pending.delete(requestId)\n sinks.replaceNetworkRequest(requestId, finalized)\n } catch (err) {\n log('warn', `responseCompleted threw: ${errorMessage(err)}`)\n }\n}\n\nasync function attachNetworkInspector(\n driver: unknown,\n factory: InspectorFactory,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): Promise<boolean> {\n try {\n const inspector = (await factory(driver)) as NetworkInspector\n const pending = new Map<string, NetworkRequest>()\n await inspector.beforeRequestSent((e) =>\n handleBidiRequestSent(e, pending, sinks, log)\n )\n await inspector.responseCompleted((e) =>\n handleBidiResponseCompleted(e, pending, sinks, log)\n )\n log('info', '✓ BiDi NetworkInspector attached (request + response)')\n return true\n } catch (err) {\n log('warn', `BiDi NetworkInspector attach failed: ${errorMessage(err)}`)\n return false\n }\n}\n\n/**\n * Attach the selenium-webdriver BiDi LogInspector + NetworkInspector to a\n * driver and route their events into the given sinks. Returns `true` when at\n * least one inspector connected — caller can disable the equivalent\n * script-injection collectors to avoid duplicates.\n *\n * Tolerant of older / non-BiDi runtimes: if either submodule fails to load\n * or the inspector factory throws (driver session doesn't have webSocketUrl\n * capability set, etc.), the corresponding stream is silently skipped and\n * the function returns false.\n *\n * @param onLog Optional callback for adapter-side logging. Receives ('info' |\n * 'warn', message) on lifecycle events. Default: silent — adapters wire their\n * own logger when they want visibility into BiDi attach state.\n */\nexport async function attachBidiHandlers(\n driver: unknown,\n sinks: BidiHandlerSinks,\n onLog?: (level: 'info' | 'warn', message: string) => void\n): Promise<boolean> {\n const log: BidiLogger = (level, message) => onLog?.(level, message)\n const logFactory =\n loadSeleniumSubmodule<InspectorFactory>('bidi/logInspector')\n const networkFactory = loadSeleniumSubmodule<InspectorFactory>(\n 'bidi/networkInspector'\n )\n\n let attached = 0\n if (typeof logFactory === 'function') {\n if (await attachLogInspector(driver, logFactory, sinks, log)) {\n attached++\n }\n } else {\n log('info', 'selenium-webdriver/bidi/logInspector not available — skipping')\n }\n if (typeof networkFactory === 'function') {\n if (await attachNetworkInspector(driver, networkFactory, sinks, log)) {\n attached++\n }\n } else {\n log(\n 'info',\n 'selenium-webdriver/bidi/networkInspector not available — skipping'\n )\n }\n return attached > 0\n}\n\n/**\n * Flatten BiDi's `Array<{name, value:{value|type}}>` header shape to a\n * lowercased `Record<string, string>`. Exported so adapter-side helpers can\n * reuse it for their own header normalization.\n */\nexport function arrayHeadersToObject(\n headers: unknown\n): Record<string, string> | undefined {\n if (!Array.isArray(headers)) {\n return undefined\n }\n const out: Record<string, string> = {}\n for (const h of headers as Array<{\n name?: string\n value?: string | { value?: string; type?: string }\n }>) {\n const name = String(h?.name ?? '').toLowerCase()\n if (!name) {\n continue\n }\n const v = h?.value\n out[name] =\n typeof v === 'string'\n ? v\n : typeof (v as { value?: string })?.value === 'string'\n ? (v as { value: string }).value\n : JSON.stringify(v ?? '')\n }\n return out\n}\n","import type { ConsoleLog, LogLevel, LogSource } from '@wdio/devtools-shared'\n\n/**\n * Console methods we intercept to forward test/runner-process output into the\n * UI Console tab.\n */\nexport const CONSOLE_METHODS = ['log', 'info', 'warn', 'error'] as const\n\n/**\n * Strips ANSI escape sequences (colour codes, cursor moves, etc.) from\n * terminal output so the UI Console renders plain text. The pattern accepts\n * any trailing letter, not just `m`, so cursor/style sequences are handled\n * too.\n */\nexport const ANSI_REGEX = /\\x1b\\[[?]?[0-9;]*[A-Za-z]/g\n\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI_REGEX, '')\n}\n\n/**\n * Log-level detection patterns, applied in priority order (highest to\n * lowest). The first matching pattern wins.\n */\nexport const LOG_LEVEL_PATTERNS: ReadonlyArray<{\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n pattern: RegExp\n}> = [\n { level: 'trace', pattern: /\\btrace\\b/i },\n { level: 'debug', pattern: /\\bdebug\\b/i },\n { level: 'info', pattern: /\\binfo\\b/i },\n { level: 'warn', pattern: /\\bwarn(ing)?\\b/i },\n { level: 'error', pattern: /\\berror\\b/i }\n] as const\n\n/** Visual indicators that suggest error-level logs in unstructured output. */\nexport const ERROR_INDICATORS = ['✗', 'failed', 'failure'] as const\n\n/**\n * Matches the leading Braille spinner glyphs that runners (Nightwatch CLI,\n * Selenium tooling) emit for in-place progress updates. Adapters skip lines\n * that match this so the dashboard's Console tab isn't flooded with frames.\n */\nexport const SPINNER_RE = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/u\n\n/**\n * Filter out terminal/stream lines that would feed back into the WS bridge\n * and cause an infinite forwarding loop: pino JSON output, [SESSION] markers,\n * backend logger lines, Jest console framing, and bare stack-frame lines.\n *\n * Adapters call this from their stream-patch before forwarding lines to the\n * UI Console tab. Combine with SPINNER_RE for full noise filtering.\n */\nexport function isInternalStreamLine(line: string): boolean {\n const t = line.trim()\n if (t.startsWith('{\"') || t.startsWith('[SESSION]')) {\n return true\n }\n if (t.includes('@wdio/devtools-backend')) {\n return true\n }\n if (/^console\\.(log|info|warn|error|debug|trace)$/.test(t)) {\n return true\n }\n if (/^at\\s.+:\\d+:\\d+\\)?$/.test(t)) {\n return true\n }\n return false\n}\n\n/** Enum-style accessor for the canonical LogSource values from shared. */\nexport const LOG_SOURCES = {\n BROWSER: 'browser',\n TEST: 'test',\n TERMINAL: 'terminal'\n} as const satisfies Record<string, LogSource>\n\nexport type { LogSource } from '@wdio/devtools-shared'\n\n/**\n * Classify a line of unstructured terminal output by scanning for log-level\n * keywords. Falls back to `'log'` when no pattern matches.\n */\nexport function detectLogLevel(text: string): LogLevel {\n const normalised = stripAnsi(text).toLowerCase()\n for (const { level, pattern } of LOG_LEVEL_PATTERNS) {\n if (pattern.test(normalised)) {\n return level\n }\n }\n if (ERROR_INDICATORS.some((i) => normalised.includes(i.toLowerCase()))) {\n return 'error'\n }\n return 'log'\n}\n\n/** Build a ConsoleLog entry tagged with the supplied source. */\nexport function createConsoleLogEntry(\n type: LogLevel,\n args: unknown[],\n source: LogSource = LOG_SOURCES.TEST\n): ConsoleLog {\n return { timestamp: Date.now(), type, args, source }\n}\n\n/**\n * Map raw Chrome browser-log entries (the shape returned by both\n * `driver.manage().logs().get('browser')` in selenium-webdriver and\n * `browser.getLog('browser')` in nightwatch) into the dashboard's typed\n * ConsoleLog shape, tagged as source='browser'. Each entry's Chrome level\n * (`SEVERE` / `WARNING` / `INFO` / `DEBUG`) is normalised through\n * {@link chromeLogLevelToLogLevel}.\n */\nexport function mapChromeBrowserLogs(\n entries: Array<{ level: unknown; message: string; timestamp: number }>\n): ConsoleLog[] {\n return entries.map((entry) => ({\n timestamp: entry.timestamp,\n type: chromeLogLevelToLogLevel(\n entry.level as string | { value?: number; name?: string }\n ),\n args: [entry.message],\n source: LOG_SOURCES.BROWSER\n }))\n}\n\n/**\n * Map a Chrome DevTools log-level string (or `{name, value}` object) to our\n * `LogLevel` union. Used by CDP/BiDi consumers that surface browser-side\n * console output through SEVERE/WARNING/INFO/DEBUG severity names.\n */\nexport function chromeLogLevelToLogLevel(\n level: string | { value?: number; name?: string }\n): LogLevel {\n const levelName = (\n typeof level === 'object' ? (level?.name ?? '') : (level ?? '')\n ).toUpperCase()\n switch (levelName) {\n case 'SEVERE':\n return 'error'\n case 'WARNING':\n return 'warn'\n case 'INFO':\n return 'info'\n case 'DEBUG':\n return 'debug'\n default:\n return 'log'\n }\n}\n","import * as net from 'node:net'\n\n/** Kept exported here so importers predating the split keep resolving; the\n * implementation is the browser-safe leaf `request-type.ts`. */\nexport { getRequestType } from './request-type.js'\n\n/**\n * Return true if the given TCP port on `hostname` cannot be bound for\n * listening (already in use, or otherwise unavailable).\n */\nexport function isPortInUse(port: number, hostname: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n server.once('error', () => resolve(true))\n server.once('listening', () => server.close(() => resolve(false)))\n server.listen(port, hostname)\n })\n}\n\n/**\n * Walk upward from `startPort` until a free port is found and return it.\n * Silent: callers that want to log retries should wrap this themselves.\n */\nexport async function findFreePort(\n startPort: number,\n hostname: string\n): Promise<number> {\n let port = startPort\n while (await isPortInUse(port, hostname)) {\n port++\n }\n return port\n}\n","import type { RequestType } from '@wdio/devtools-shared'\n\n/**\n * Classify an HTTP request into a {@link RequestType}, preferring the response\n * `mimeType` and falling back to URL extension heuristics. The categories are\n * enumerated once, in shared's `REQUEST_TYPES`, so this text can't drift from\n * them. A shape it can't place returns `'xhr'` — never the vocabulary's\n * `'other'`, which is reserved for producers that classify nothing at all.\n *\n * A pure leaf module: the capture side's only classifier, importable from any\n * runtime. `net.ts` re-exports it under the same name for existing consumers,\n * but that module pulls in `node:net` and so can't be bundled for the browser.\n */\nexport function getRequestType(url: string, mimeType?: string): RequestType {\n const contentType = mimeType?.toLowerCase() ?? ''\n const urlLower = url.toLowerCase()\n if (contentType.includes('text/html')) {\n return 'document'\n }\n if (contentType.includes('text/css')) {\n return 'stylesheet'\n }\n if (\n contentType.includes('javascript') ||\n contentType.includes('ecmascript')\n ) {\n return 'script'\n }\n if (contentType.includes('image/')) {\n return 'image'\n }\n if (contentType.includes('font/') || contentType.includes('woff')) {\n return 'font'\n }\n if (contentType.includes('application/json')) {\n return 'fetch'\n }\n if (urlLower.endsWith('.html') || urlLower.endsWith('.htm')) {\n return 'document'\n }\n if (urlLower.endsWith('.css')) {\n return 'stylesheet'\n }\n if (urlLower.endsWith('.js') || urlLower.endsWith('.mjs')) {\n return 'script'\n }\n if (/\\.(png|jpg|jpeg|gif|svg|webp|ico)$/.test(urlLower)) {\n return 'image'\n }\n if (/\\.(woff|woff2|ttf|eot|otf)$/.test(urlLower)) {\n return 'font'\n }\n return 'xhr'\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/plugin-global-key.ts
24
+ var plugin_global_key_exports = {};
25
+ __export(plugin_global_key_exports, {
26
+ PLUGIN_GLOBAL_KEY: () => PLUGIN_GLOBAL_KEY
27
+ });
28
+ var PLUGIN_GLOBAL_KEY;
29
+ var init_plugin_global_key = __esm({
30
+ "src/plugin-global-key.ts"() {
31
+ "use strict";
32
+ PLUGIN_GLOBAL_KEY = "__nightwatchDevtoolsPlugin";
33
+ }
34
+ });
35
+
36
+ // src/helpers/cucumberHooks.cts
37
+ var { Before, After, BeforeStep, AfterStep } = require("@cucumber/cucumber");
38
+ var { PLUGIN_GLOBAL_KEY: PLUGIN_GLOBAL_KEY2 } = (init_plugin_global_key(), __toCommonJS(plugin_global_key_exports));
39
+ Before({ order: 1e3 }, async function({ pickle }) {
40
+ const plugin = globalThis[PLUGIN_GLOBAL_KEY2];
41
+ if (this.browser && plugin) {
42
+ await plugin.cucumberBefore(this.browser, pickle);
43
+ }
44
+ });
45
+ After({ order: 1e3 }, async function({ result }) {
46
+ const plugin = globalThis[PLUGIN_GLOBAL_KEY2];
47
+ if (this.browser && plugin) {
48
+ await plugin.cucumberBeforeQuit(this.browser, result);
49
+ }
50
+ });
51
+ After({ order: -1 }, async function({ result, pickle }) {
52
+ const plugin = globalThis[PLUGIN_GLOBAL_KEY2];
53
+ if (this.browser && plugin) {
54
+ await plugin.cucumberAfter(this.browser, result, pickle);
55
+ }
56
+ });
57
+ BeforeStep({ order: 1e3 }, async function({ pickleStep, pickle }) {
58
+ const plugin = globalThis[PLUGIN_GLOBAL_KEY2];
59
+ if (this.browser && plugin) {
60
+ await plugin.cucumberBeforeStep(this.browser, pickleStep, pickle);
61
+ }
62
+ });
63
+ AfterStep({ order: 1e3 }, async function({ result, pickleStep, pickle }) {
64
+ const plugin = globalThis[PLUGIN_GLOBAL_KEY2];
65
+ if (this.browser && plugin) {
66
+ await plugin.cucumberAfterStep(this.browser, result, pickleStep, pickle);
67
+ }
68
+ });
package/dist/index.d.ts CHANGED
@@ -115,6 +115,7 @@ declare class NightwatchDevToolsPlugin {
115
115
  constructor(options?: DevToolsOptions);
116
116
  before(): Promise<void>;
117
117
  cucumberBefore(browser: NightwatchBrowser, pickle: CucumberPickle): Promise<void>;
118
+ cucumberBeforeQuit(browser: NightwatchBrowser, result: CucumberResult): Promise<void>;
118
119
  cucumberAfter(browser: NightwatchBrowser, result: CucumberResult, pickle: CucumberPickle): Promise<void>;
119
120
  cucumberBeforeStep(browser: NightwatchBrowser, pickleStep: CucumberPickleStep, pickle: CucumberPickle): Promise<void>;
120
121
  cucumberAfterStep(browser: NightwatchBrowser, result: CucumberResult, pickleStep: CucumberPickleStep, pickle: CucumberPickle): Promise<void>;