@wdio/nightwatch-devtools 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/{bidi-AZX3WRGJ.js → bidi-C67DYUCA.js} +2 -2
- package/dist/{chunk-TUK7ZLQI.js → chunk-DRZC45YG.js} +355 -184
- package/dist/chunk-DRZC45YG.js.map +1 -0
- package/dist/index.js +503 -256
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-TUK7ZLQI.js.map +0 -1
- /package/dist/{bidi-AZX3WRGJ.js.map → bidi-C67DYUCA.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CAPTURE_PERFORMANCE_SCRIPT,
|
|
3
|
-
|
|
3
|
+
COLLECTOR_INJECT_SCRIPT,
|
|
4
|
+
COLLECTOR_READY_SCRIPT,
|
|
4
5
|
NETWORK_CAPTURE_SOURCE,
|
|
5
6
|
NativeNetworkCapture,
|
|
6
7
|
REUSE_ENV,
|
|
8
|
+
RUNNER_ENV,
|
|
7
9
|
RetryTracker,
|
|
8
10
|
SCREENCAST_DEFAULTS,
|
|
9
11
|
ScreencastRecorderBase,
|
|
@@ -18,14 +20,17 @@ import {
|
|
|
18
20
|
captureActionSnapshot,
|
|
19
21
|
captureAndAttachScreenshot,
|
|
20
22
|
captureAndAttachVideo,
|
|
23
|
+
clearReadValues,
|
|
21
24
|
collectSuiteTestMetadata,
|
|
22
25
|
collectorDrainExpression,
|
|
26
|
+
commandFailure,
|
|
23
27
|
computeSuiteFinalStateStrict,
|
|
24
28
|
computeSuiteRunningState,
|
|
25
29
|
createSuiteStats,
|
|
26
30
|
createTestStats,
|
|
27
31
|
deterministicUid,
|
|
28
32
|
drainCollectorWithRecovery,
|
|
33
|
+
encodeInitialArgs,
|
|
29
34
|
errorMessage,
|
|
30
35
|
finalizeScreencast,
|
|
31
36
|
finalizeTraceExport,
|
|
@@ -37,9 +42,9 @@ import {
|
|
|
37
42
|
getCallSourceFromStack,
|
|
38
43
|
getRequestType,
|
|
39
44
|
isUserCodeFrame,
|
|
45
|
+
isWaitCommand,
|
|
40
46
|
lastRenderedScreenshot,
|
|
41
47
|
loadInjectableScript,
|
|
42
|
-
mapChromeBrowserLogs,
|
|
43
48
|
mapCommandToAction,
|
|
44
49
|
matcherAssertionToCommandLog,
|
|
45
50
|
normalizeFilePath,
|
|
@@ -65,7 +70,7 @@ import {
|
|
|
65
70
|
webdriverExecute,
|
|
66
71
|
webdriverGet,
|
|
67
72
|
webdriverPost
|
|
68
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-DRZC45YG.js";
|
|
69
74
|
|
|
70
75
|
// src/index.ts
|
|
71
76
|
import { fileURLToPath } from "url";
|
|
@@ -169,14 +174,18 @@ async function emitTestArtifacts(input) {
|
|
|
169
174
|
// src/helpers/assertCapture.ts
|
|
170
175
|
import logger from "@wdio/logger";
|
|
171
176
|
var log = logger("@wdio/nightwatch-devtools:assertCapture");
|
|
172
|
-
function wireAssertCapture(getCapturer, getTestUid) {
|
|
177
|
+
function wireAssertCapture(getCapturer, getTestUid, scopeReadValues = () => {
|
|
178
|
+
}) {
|
|
173
179
|
patchNodeAssert(
|
|
174
180
|
(cmd) => captureAssert(getCapturer(), getTestUid(), cmd),
|
|
175
181
|
(level, message) => log[level](message),
|
|
176
182
|
// No handle resolver: Nightwatch commands take selector strings, so an
|
|
177
183
|
// element object never reaches an assert — only the value's provenance,
|
|
178
184
|
// recorded by the command path (BrowserProxy), can name the target.
|
|
179
|
-
(args) =>
|
|
185
|
+
(args) => {
|
|
186
|
+
scopeReadValues();
|
|
187
|
+
return resolveAssertTargetFromArgs(args);
|
|
188
|
+
}
|
|
180
189
|
);
|
|
181
190
|
}
|
|
182
191
|
function captureAssert(capturer, testUid, cmd) {
|
|
@@ -201,7 +210,7 @@ function captureAssert(capturer, testUid, cmd) {
|
|
|
201
210
|
|
|
202
211
|
// src/index.ts
|
|
203
212
|
import { stop as stopBackend } from "@wdio/devtools-backend";
|
|
204
|
-
import
|
|
213
|
+
import logger16 from "@wdio/logger";
|
|
205
214
|
|
|
206
215
|
// src/run-lifecycle.ts
|
|
207
216
|
import * as fs2 from "fs";
|
|
@@ -258,6 +267,7 @@ var TIMING = {
|
|
|
258
267
|
};
|
|
259
268
|
var BOOLEAN_COMMAND_PATTERN = /^waitFor|^is[A-Z]|^has[A-Z]|(Visible|Present|Enabled|Selected|NotVisible|NotPresent)$/;
|
|
260
269
|
var NAVIGATION_COMMANDS = ["url", "navigate", "navigateTo"];
|
|
270
|
+
var NIGHTWATCH_CONFIG_FLAGS = ["--config", "-c"];
|
|
261
271
|
var CONFIG_FILENAMES = [
|
|
262
272
|
"nightwatch.conf.cjs",
|
|
263
273
|
"nightwatch.conf.js",
|
|
@@ -271,10 +281,13 @@ import * as fs from "fs";
|
|
|
271
281
|
import * as path from "path";
|
|
272
282
|
import { parse as parseStackTrace } from "stacktrace-parser";
|
|
273
283
|
function determineTestState(testcase) {
|
|
274
|
-
if (testcase.
|
|
284
|
+
if (testcase.failed > 0 || testcase.errors > 0) {
|
|
285
|
+
return "failed";
|
|
286
|
+
}
|
|
287
|
+
if (testcase.passed === 0 && testcase.skipped > 0) {
|
|
275
288
|
return "skipped";
|
|
276
289
|
}
|
|
277
|
-
return
|
|
290
|
+
return "passed";
|
|
278
291
|
}
|
|
279
292
|
function getTestIcon(state) {
|
|
280
293
|
return state === "passed" ? "\u2705" : state === "skipped" ? "\u23ED" : "\u274C";
|
|
@@ -295,13 +308,53 @@ function buildPluginMetadataOptions(input) {
|
|
|
295
308
|
return {
|
|
296
309
|
framework: nightwatchRunnerId(input.isCucumberRunner),
|
|
297
310
|
configFile: input.configPath,
|
|
298
|
-
baseDir: process.cwd()
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
311
|
+
baseDir: process.cwd()
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function isNightwatchCli(entry) {
|
|
315
|
+
if (!entry) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const script = fs.realpathSync(entry);
|
|
320
|
+
const pkgDir = path.dirname(path.dirname(script));
|
|
321
|
+
const pkg = JSON.parse(
|
|
322
|
+
fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")
|
|
323
|
+
);
|
|
324
|
+
return path.basename(path.dirname(script)) === "bin" && pkg.name === "nightwatch";
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function captureNightwatchSelection(argv = process.argv, cwd = process.cwd()) {
|
|
330
|
+
if (!isNightwatchCli(argv[1])) {
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
const spec = (arg) => {
|
|
334
|
+
const local = path.resolve(cwd, arg);
|
|
335
|
+
return fs.existsSync(local) ? local : arg;
|
|
304
336
|
};
|
|
337
|
+
const args = [];
|
|
338
|
+
const rest = argv.slice(2);
|
|
339
|
+
for (let i = 0; i < rest.length; i++) {
|
|
340
|
+
const arg = rest[i];
|
|
341
|
+
if (arg === "--") {
|
|
342
|
+
args.push(arg, ...rest.slice(i + 1).map(spec));
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
if (!arg.startsWith("-")) {
|
|
346
|
+
args.push(spec(arg));
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const next = rest[i + 1];
|
|
350
|
+
const takesNext = !arg.includes("=") && !arg.startsWith("--no-") && next !== void 0 && !next.startsWith("-");
|
|
351
|
+
const value = takesNext ? rest[++i] : void 0;
|
|
352
|
+
if (NIGHTWATCH_CONFIG_FLAGS.includes(arg.split("=")[0])) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
args.push(arg, ...value === void 0 ? [] : [value]);
|
|
356
|
+
}
|
|
357
|
+
return args.length ? args : void 0;
|
|
305
358
|
}
|
|
306
359
|
function generateStableUid2(itemOrFile, name) {
|
|
307
360
|
let file, testName;
|
|
@@ -522,6 +575,13 @@ async function runPluginBefore(ctx) {
|
|
|
522
575
|
ctx.options.port,
|
|
523
576
|
ctx.options.hostname
|
|
524
577
|
);
|
|
578
|
+
const selection = captureNightwatchSelection();
|
|
579
|
+
if (selection) {
|
|
580
|
+
process.env[RUNNER_ENV.NIGHTWATCH_INITIAL_ARGS] = encodeInitialArgs(selection);
|
|
581
|
+
} else {
|
|
582
|
+
delete process.env[RUNNER_ENV.NIGHTWATCH_INITIAL_ARGS];
|
|
583
|
+
}
|
|
584
|
+
process.env[RUNNER_ENV.RUNNER_CWD] ??= process.cwd();
|
|
525
585
|
log2.info("\u{1F680} Starting DevTools backend...");
|
|
526
586
|
const { port } = await start(ctx.options);
|
|
527
587
|
ctx.options.port = port;
|
|
@@ -639,7 +699,7 @@ async function waitForDevtoolsBrowserClose(ctx) {
|
|
|
639
699
|
}
|
|
640
700
|
|
|
641
701
|
// src/session-init.ts
|
|
642
|
-
import
|
|
702
|
+
import logger11 from "@wdio/logger";
|
|
643
703
|
|
|
644
704
|
// src/helpers/webdriverHttp.ts
|
|
645
705
|
import logger3 from "@wdio/logger";
|
|
@@ -674,11 +734,29 @@ function configuredWebDriver(browser) {
|
|
|
674
734
|
getProp(opts, "webdriver"),
|
|
675
735
|
getProp(nightwatchSettings, "webdriver")
|
|
676
736
|
];
|
|
677
|
-
const
|
|
737
|
+
const values = (...keys) => sources.map((source) => firstDefined(source, ...keys));
|
|
738
|
+
const pick = (...keys) => values(...keys).find(Boolean);
|
|
678
739
|
return {
|
|
679
740
|
host: pick("host", "server_address"),
|
|
680
741
|
port: pick("port"),
|
|
681
|
-
pathPrefix: pick("default_path_prefix")
|
|
742
|
+
pathPrefix: pick("default_path_prefix"),
|
|
743
|
+
// `ssl: false` is a setting, not an absence, so it must not fall through to a later source.
|
|
744
|
+
ssl: values("ssl").find((v) => v !== void 0),
|
|
745
|
+
username: pick("username"),
|
|
746
|
+
accessKey: values("access_key").find((v) => v !== void 0)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function connection(configured, hostname, port) {
|
|
750
|
+
const { pathPrefix, ssl, username, accessKey } = configured;
|
|
751
|
+
const secure = ssl === void 0 ? port === 443 : Boolean(ssl);
|
|
752
|
+
const hasCredentials = Boolean(username) && accessKey !== void 0;
|
|
753
|
+
return {
|
|
754
|
+
hostname,
|
|
755
|
+
port,
|
|
756
|
+
protocol: secure ? "https" : "http",
|
|
757
|
+
path: typeof pathPrefix === "string" ? pathPrefix : void 0,
|
|
758
|
+
...hasCredentials ? { user: String(username), key: String(accessKey) } : {},
|
|
759
|
+
onWarn: (message) => log3.warn(message)
|
|
682
760
|
};
|
|
683
761
|
}
|
|
684
762
|
function resolveWebDriverAddress(browser) {
|
|
@@ -690,11 +768,7 @@ function resolveWebDriverAddress(browser) {
|
|
|
690
768
|
}
|
|
691
769
|
function address(browser) {
|
|
692
770
|
const { driverHost, driverPort } = resolveWebDriverAddress(browser);
|
|
693
|
-
return
|
|
694
|
-
hostname: driverHost,
|
|
695
|
-
port: driverPort,
|
|
696
|
-
onWarn: (message) => log3.warn(message)
|
|
697
|
-
};
|
|
771
|
+
return connection(configuredWebDriver(browser), driverHost, driverPort);
|
|
698
772
|
}
|
|
699
773
|
function sessionId(browser) {
|
|
700
774
|
return browser.sessionId;
|
|
@@ -709,20 +783,19 @@ function webdriverPost2(browser, path5, body) {
|
|
|
709
783
|
}
|
|
710
784
|
function sessionExecutor(browser) {
|
|
711
785
|
const id = sessionId(browser);
|
|
712
|
-
const
|
|
713
|
-
if (!id || !port) {
|
|
786
|
+
const configured = configuredWebDriver(browser);
|
|
787
|
+
if (!id || !configured.port) {
|
|
714
788
|
return void 0;
|
|
715
789
|
}
|
|
716
|
-
const at =
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
};
|
|
790
|
+
const at = connection(
|
|
791
|
+
configured,
|
|
792
|
+
String(configured.host || "localhost"),
|
|
793
|
+
Number(configured.port)
|
|
794
|
+
);
|
|
722
795
|
return (script, args) => webdriverExecute(at, id, script, args);
|
|
723
796
|
}
|
|
724
|
-
function webdriverExecute2(browser, body) {
|
|
725
|
-
return webdriverPost2(browser, "execute/sync", { script: body, args
|
|
797
|
+
function webdriverExecute2(browser, body, args = []) {
|
|
798
|
+
return webdriverPost2(browser, "execute/sync", { script: body, args });
|
|
726
799
|
}
|
|
727
800
|
|
|
728
801
|
// src/session.ts
|
|
@@ -960,12 +1033,6 @@ function dedupeNetworkRequests(incoming, existing) {
|
|
|
960
1033
|
|
|
961
1034
|
// src/session.ts
|
|
962
1035
|
var log4 = logger4("@wdio/nightwatch-devtools:SessionCapturer");
|
|
963
|
-
function unwrapDriverValue(result) {
|
|
964
|
-
if (result && typeof result === "object" && "value" in result) {
|
|
965
|
-
return result.value;
|
|
966
|
-
}
|
|
967
|
-
return result;
|
|
968
|
-
}
|
|
969
1036
|
var SessionCapturer = class extends SessionCapturerBase {
|
|
970
1037
|
#browser;
|
|
971
1038
|
// True once BiDi inspectors are attached — the per-command perf-log network
|
|
@@ -1029,6 +1096,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1029
1096
|
const commandId = this.commandCounter++;
|
|
1030
1097
|
const commandLogEntry = {
|
|
1031
1098
|
_id: commandId,
|
|
1099
|
+
id: commandId,
|
|
1032
1100
|
command,
|
|
1033
1101
|
args,
|
|
1034
1102
|
result,
|
|
@@ -1038,7 +1106,8 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1038
1106
|
sequence: meta.sequence,
|
|
1039
1107
|
callSource: meta.callSource,
|
|
1040
1108
|
testUid: meta.testUid,
|
|
1041
|
-
selector: meta.selector
|
|
1109
|
+
selector: meta.selector,
|
|
1110
|
+
parentId: meta.parentId
|
|
1042
1111
|
};
|
|
1043
1112
|
this.commandsLog.push(commandLogEntry);
|
|
1044
1113
|
const isNavigationCommand = NAVIGATION_COMMANDS.some(
|
|
@@ -1081,15 +1150,18 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1081
1150
|
return;
|
|
1082
1151
|
}
|
|
1083
1152
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1153
|
+
const payload = await webdriverExecute2(
|
|
1154
|
+
this.#browser,
|
|
1155
|
+
CAPTURE_PERFORMANCE_SCRIPT
|
|
1087
1156
|
);
|
|
1088
|
-
applyPerformanceData(
|
|
1157
|
+
if (applyPerformanceData(
|
|
1089
1158
|
commandLogEntry,
|
|
1090
|
-
payload,
|
|
1159
|
+
payload ?? void 0,
|
|
1091
1160
|
typeof args[0] === "string" ? args[0] : void 0
|
|
1092
|
-
);
|
|
1161
|
+
) && // A retry may have replaced the row meanwhile; replacing it again would bring the old row back.
|
|
1162
|
+
this.commandsLog.includes(commandLogEntry)) {
|
|
1163
|
+
this.sendReplaceCommand(commandLogEntry.timestamp, commandLogEntry);
|
|
1164
|
+
}
|
|
1093
1165
|
}
|
|
1094
1166
|
/**
|
|
1095
1167
|
* Ingest a pre-built assertion entry (native `browser.assert`/`verify`
|
|
@@ -1119,7 +1191,8 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1119
1191
|
const idx = this.commandsLog.findIndex(
|
|
1120
1192
|
(c) => c._id === oldId
|
|
1121
1193
|
);
|
|
1122
|
-
const
|
|
1194
|
+
const previous = idx !== -1 ? this.commandsLog[idx] : void 0;
|
|
1195
|
+
const oldTimestamp = previous?.timestamp ?? 0;
|
|
1123
1196
|
if (idx !== -1) {
|
|
1124
1197
|
this.commandsLog.splice(idx, 1);
|
|
1125
1198
|
}
|
|
@@ -1128,6 +1201,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1128
1201
|
const commandId = this.commandCounter++;
|
|
1129
1202
|
const entry = {
|
|
1130
1203
|
_id: commandId,
|
|
1204
|
+
id: previous?.id ?? commandId,
|
|
1131
1205
|
command,
|
|
1132
1206
|
args,
|
|
1133
1207
|
result,
|
|
@@ -1137,11 +1211,61 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1137
1211
|
sequence: meta.sequence,
|
|
1138
1212
|
callSource: meta.callSource,
|
|
1139
1213
|
testUid: meta.testUid,
|
|
1140
|
-
selector: meta.selector
|
|
1214
|
+
selector: meta.selector,
|
|
1215
|
+
parentId: meta.parentId
|
|
1141
1216
|
};
|
|
1142
1217
|
this.commandsLog.push(entry);
|
|
1143
1218
|
return { entry, oldTimestamp };
|
|
1144
1219
|
}
|
|
1220
|
+
/** Rows {@link openCommand} sent that have not ended yet. */
|
|
1221
|
+
#openRows = /* @__PURE__ */ new Set();
|
|
1222
|
+
/** The newest row that has ended: a row still running has caused nothing yet. */
|
|
1223
|
+
#newestEndedTimestamp() {
|
|
1224
|
+
for (let i = this.commandsLog.length - 1; i >= 0; i--) {
|
|
1225
|
+
if (!this.#openRows.has(this.commandsLog[i])) {
|
|
1226
|
+
return this.commandsLog[i].timestamp;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return void 0;
|
|
1230
|
+
}
|
|
1231
|
+
/** Send a row that has started and not ended (a wait), so the rows it issues have a row to nest under while it runs. */
|
|
1232
|
+
openCommand(command, args, meta) {
|
|
1233
|
+
const id = this.commandCounter++;
|
|
1234
|
+
const entry = {
|
|
1235
|
+
_id: id,
|
|
1236
|
+
id,
|
|
1237
|
+
command,
|
|
1238
|
+
args,
|
|
1239
|
+
timestamp: meta.startTime ?? Date.now(),
|
|
1240
|
+
startTime: meta.startTime,
|
|
1241
|
+
sequence: meta.sequence,
|
|
1242
|
+
callSource: meta.callSource,
|
|
1243
|
+
testUid: meta.testUid,
|
|
1244
|
+
selector: meta.selector,
|
|
1245
|
+
parentId: meta.parentId
|
|
1246
|
+
};
|
|
1247
|
+
this.commandsLog.push(entry);
|
|
1248
|
+
this.#openRows.add(entry);
|
|
1249
|
+
this.sendCommand(entry);
|
|
1250
|
+
return entry;
|
|
1251
|
+
}
|
|
1252
|
+
/** End a row {@link openCommand} sent, stamped at completion, replaced by its `id` and moved to where it completed; a row that never ran keeps its place. */
|
|
1253
|
+
closeCommand(entry, result, error, timestamp, ran = true) {
|
|
1254
|
+
this.#openRows.delete(entry);
|
|
1255
|
+
const at = this.commandsLog.lastIndexOf(entry);
|
|
1256
|
+
if (ran && at !== -1) {
|
|
1257
|
+
this.commandsLog.splice(at, 1);
|
|
1258
|
+
this.commandsLog.push(entry);
|
|
1259
|
+
}
|
|
1260
|
+
const oldTimestamp = entry.timestamp;
|
|
1261
|
+
entry.result = result;
|
|
1262
|
+
entry.error = serializeError(error);
|
|
1263
|
+
entry.timestamp = timestamp;
|
|
1264
|
+
if (!error) {
|
|
1265
|
+
this.#queueActionSnapshot(entry.command, timestamp);
|
|
1266
|
+
}
|
|
1267
|
+
this.sendReplaceCommand(oldTimestamp, entry);
|
|
1268
|
+
}
|
|
1145
1269
|
/** Birth time of the document this capturer last observed, so a replacement
|
|
1146
1270
|
* is still visible to a poll that starts after the navigation committed. */
|
|
1147
1271
|
lastDocumentOrigin;
|
|
@@ -1197,11 +1321,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1197
1321
|
if (!replaced) {
|
|
1198
1322
|
return;
|
|
1199
1323
|
}
|
|
1200
|
-
await this.captureTrace(
|
|
1201
|
-
browser,
|
|
1202
|
-
true,
|
|
1203
|
-
this.commandsLog[this.commandsLog.length - 1]?.timestamp
|
|
1204
|
-
);
|
|
1324
|
+
await this.captureTrace(browser, true, this.#newestEndedTimestamp());
|
|
1205
1325
|
}
|
|
1206
1326
|
/**
|
|
1207
1327
|
* Take a screenshot by calling the WebDriver HTTP endpoint directly, bypassing
|
|
@@ -1233,23 +1353,21 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1233
1353
|
try {
|
|
1234
1354
|
const alreadyPresent = await webdriverExecute2(
|
|
1235
1355
|
browser,
|
|
1236
|
-
|
|
1356
|
+
COLLECTOR_READY_SCRIPT
|
|
1237
1357
|
);
|
|
1238
1358
|
if (alreadyPresent === true) {
|
|
1239
1359
|
return;
|
|
1240
1360
|
}
|
|
1241
1361
|
const scriptContent = await loadInjectableScript();
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
`;
|
|
1248
|
-
const injected = await webdriverExecute2(browser, injectionScript);
|
|
1362
|
+
const injected = await webdriverExecute2(
|
|
1363
|
+
browser,
|
|
1364
|
+
COLLECTOR_INJECT_SCRIPT,
|
|
1365
|
+
[scriptContent]
|
|
1366
|
+
);
|
|
1249
1367
|
const hasCollector = injected === true || await pollUntilReady(
|
|
1250
1368
|
async () => await webdriverExecute2(
|
|
1251
1369
|
browser,
|
|
1252
|
-
|
|
1370
|
+
COLLECTOR_READY_SCRIPT
|
|
1253
1371
|
) === true,
|
|
1254
1372
|
{ checkFirst: true }
|
|
1255
1373
|
);
|
|
@@ -1263,23 +1381,6 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1263
1381
|
throw err;
|
|
1264
1382
|
}
|
|
1265
1383
|
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Capture Chrome DevTools browser console logs via WebDriver log API.
|
|
1268
|
-
* Requires loggingPrefs: { browser: 'ALL' } in Chrome capabilities.
|
|
1269
|
-
*/
|
|
1270
|
-
async captureBrowserLogs(browser) {
|
|
1271
|
-
try {
|
|
1272
|
-
const rawLogs = await browser.getLog("browser");
|
|
1273
|
-
const logs = unwrapDriverValue(rawLogs);
|
|
1274
|
-
if (!Array.isArray(logs) || logs.length === 0) {
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
const entries = mapChromeBrowserLogs(logs);
|
|
1278
|
-
this.consoleLogs.push(...entries);
|
|
1279
|
-
this.sendUpstream("consoleLogs", entries);
|
|
1280
|
-
} catch {
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
1384
|
/**
|
|
1284
1385
|
* Parse Chrome performance logs to extract network request entries.
|
|
1285
1386
|
* Requires loggingPrefs: { performance: 'ALL' } in Chrome capabilities.
|
|
@@ -1288,21 +1389,15 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1288
1389
|
if (this.bidiActive || !this.perfLogsRequested) {
|
|
1289
1390
|
return;
|
|
1290
1391
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
} catch (err) {
|
|
1302
|
-
const msg = errorMessage(err) ?? "";
|
|
1303
|
-
if (!msg.includes("log type") && !msg.includes("performance")) {
|
|
1304
|
-
log4.warn(`Performance log capture failed: ${msg}`);
|
|
1305
|
-
}
|
|
1392
|
+
const logs = await webdriverPost2(browser, "se/log", {
|
|
1393
|
+
type: "performance"
|
|
1394
|
+
});
|
|
1395
|
+
if (!Array.isArray(logs) || logs.length === 0) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const networkEntries = parseNetworkFromPerfLogs(logs);
|
|
1399
|
+
if (networkEntries.length > 0) {
|
|
1400
|
+
this.#ingestPerfLogEntries(networkEntries);
|
|
1306
1401
|
}
|
|
1307
1402
|
}
|
|
1308
1403
|
/** A log entry for a load the page already reported replaces that row in
|
|
@@ -1777,7 +1872,7 @@ var SuiteManager = class {
|
|
|
1777
1872
|
};
|
|
1778
1873
|
|
|
1779
1874
|
// src/helpers/browserProxy.ts
|
|
1780
|
-
import
|
|
1875
|
+
import logger8 from "@wdio/logger";
|
|
1781
1876
|
|
|
1782
1877
|
// src/helpers/callbackError.ts
|
|
1783
1878
|
function callbackError(result, depth = 0) {
|
|
@@ -2279,8 +2374,126 @@ var NativeAssertRecorder = class {
|
|
|
2279
2374
|
}
|
|
2280
2375
|
};
|
|
2281
2376
|
|
|
2377
|
+
// src/helpers/waitRows.ts
|
|
2378
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2379
|
+
var NOT_RUN_ERROR = "NotRunError";
|
|
2380
|
+
var WaitRows = class {
|
|
2381
|
+
constructor(capturer) {
|
|
2382
|
+
this.capturer = capturer;
|
|
2383
|
+
}
|
|
2384
|
+
capturer;
|
|
2385
|
+
#scope = new AsyncLocalStorage();
|
|
2386
|
+
#open = /* @__PURE__ */ new Set();
|
|
2387
|
+
/** The innermost open wait making this call; a scope outlives its wait, so a closed one defers to its own parent. */
|
|
2388
|
+
enclosing() {
|
|
2389
|
+
let wait = this.#scope.getStore();
|
|
2390
|
+
while (wait?.closed) {
|
|
2391
|
+
wait = wait.parent;
|
|
2392
|
+
}
|
|
2393
|
+
return wait;
|
|
2394
|
+
}
|
|
2395
|
+
/** Send the wait's row running, before the queue runs it, so its condition's commands have a row to name. */
|
|
2396
|
+
open(cmd) {
|
|
2397
|
+
const row = this.capturer.openCommand(cmd.methodName, cmd.logArgs, {
|
|
2398
|
+
testUid: cmd.testUid,
|
|
2399
|
+
callSource: cmd.callSource,
|
|
2400
|
+
startTime: cmd.invokedAt,
|
|
2401
|
+
sequence: cmd.sequence,
|
|
2402
|
+
selector: commandRowSelector(cmd.methodName, cmd.logArgs),
|
|
2403
|
+
parentId: cmd.parent?.row.id
|
|
2404
|
+
});
|
|
2405
|
+
const wait = { row, parent: cmd.parent, children: /* @__PURE__ */ new Map(), closed: false };
|
|
2406
|
+
this.#open.add(wait);
|
|
2407
|
+
return wait;
|
|
2408
|
+
}
|
|
2409
|
+
/** The args a wait is called with: a `waitUntil` condition runs every poll inside the wait's scope. */
|
|
2410
|
+
args(cmd) {
|
|
2411
|
+
const [condition, ...rest] = cmd.logArgs;
|
|
2412
|
+
const wait = cmd.wait;
|
|
2413
|
+
if (!wait || typeof condition !== "function") {
|
|
2414
|
+
return cmd.logArgs;
|
|
2415
|
+
}
|
|
2416
|
+
const scope = this.#scope;
|
|
2417
|
+
const scoped = function(...args) {
|
|
2418
|
+
return scope.run(wait, () => condition.apply(this, args));
|
|
2419
|
+
};
|
|
2420
|
+
return [scoped, ...rest];
|
|
2421
|
+
}
|
|
2422
|
+
/** End the wait's row by `id`, stamped at completion; a wait Nightwatch reports as `false` timed out. */
|
|
2423
|
+
close(wait, result, error, completedAt) {
|
|
2424
|
+
wait.closed = true;
|
|
2425
|
+
this.#open.delete(wait);
|
|
2426
|
+
const { row } = wait;
|
|
2427
|
+
const action = mapCommandToAction(row.command);
|
|
2428
|
+
const timeout = !error && action ? commandFailure({ ...row, result }, action) : void 0;
|
|
2429
|
+
this.capturer.closeCommand(
|
|
2430
|
+
row,
|
|
2431
|
+
result,
|
|
2432
|
+
error ?? (timeout && new Error(timeout.message)),
|
|
2433
|
+
completedAt
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
/** Close every wait still open as not run: Nightwatch drops a queued wait, callback and all, when an earlier assert aborts the queue. */
|
|
2437
|
+
closeUnrun() {
|
|
2438
|
+
const unrun = [...this.#open];
|
|
2439
|
+
for (const wait of unrun) {
|
|
2440
|
+
wait.closed = true;
|
|
2441
|
+
this.#open.delete(wait);
|
|
2442
|
+
const error = new Error(
|
|
2443
|
+
`${wait.row.command} did not run: the test ended before Nightwatch reached it`
|
|
2444
|
+
);
|
|
2445
|
+
error.name = NOT_RUN_ERROR;
|
|
2446
|
+
this.capturer.closeCommand(
|
|
2447
|
+
wait.row,
|
|
2448
|
+
void 0,
|
|
2449
|
+
error,
|
|
2450
|
+
wait.row.startTime ?? wait.row.timestamp,
|
|
2451
|
+
false
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
return unrun;
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
|
|
2458
|
+
// src/helpers/navigationWrap.ts
|
|
2459
|
+
import logger7 from "@wdio/logger";
|
|
2460
|
+
var log7 = logger7("@wdio/nightwatch-devtools:navigation");
|
|
2461
|
+
function wrapNavigation(browser, sessionCapturer) {
|
|
2462
|
+
const b = browser;
|
|
2463
|
+
const wrapNav = (methodName) => {
|
|
2464
|
+
if (typeof b[methodName] !== "function") {
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
const original = b[methodName].bind(browser);
|
|
2468
|
+
b[methodName] = function(...args) {
|
|
2469
|
+
if (typeof args[0] !== "string") {
|
|
2470
|
+
return original(...args);
|
|
2471
|
+
}
|
|
2472
|
+
const result = original(...args);
|
|
2473
|
+
const injectAndCapture = () => {
|
|
2474
|
+
log7.info(`[nav] ${methodName}(${args[0] ?? ""}) \u2014 injecting script`);
|
|
2475
|
+
return sessionCapturer.injectScript(browser).then(() => sessionCapturer.captureTrace(browser, true)).catch(
|
|
2476
|
+
(err) => log7.error(`Failed to inject script: ${err.message}`)
|
|
2477
|
+
);
|
|
2478
|
+
};
|
|
2479
|
+
const chainable = result;
|
|
2480
|
+
if (chainable && typeof chainable.perform === "function") {
|
|
2481
|
+
void Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2482
|
+
});
|
|
2483
|
+
return result;
|
|
2484
|
+
}
|
|
2485
|
+
return Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2486
|
+
});
|
|
2487
|
+
};
|
|
2488
|
+
};
|
|
2489
|
+
wrapNav("url");
|
|
2490
|
+
wrapNav("navigate");
|
|
2491
|
+
wrapNav("navigateTo");
|
|
2492
|
+
log7.info("\u2713 Script injection wrapped");
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2282
2495
|
// src/helpers/browserProxy.ts
|
|
2283
|
-
var
|
|
2496
|
+
var log8 = logger8("@wdio/nightwatch-devtools:browserProxy");
|
|
2284
2497
|
var BrowserProxy = class {
|
|
2285
2498
|
constructor(sessionCapturer, testManager, getCurrentTest, captureAssertions = true, observeSession) {
|
|
2286
2499
|
this.sessionCapturer = sessionCapturer;
|
|
@@ -2288,6 +2501,7 @@ var BrowserProxy = class {
|
|
|
2288
2501
|
this.getCurrentTest = getCurrentTest;
|
|
2289
2502
|
this.captureAssertions = captureAssertions;
|
|
2290
2503
|
this.observeSession = observeSession;
|
|
2504
|
+
this.waits = new WaitRows(sessionCapturer);
|
|
2291
2505
|
this.assertRecorder = new NativeAssertRecorder(
|
|
2292
2506
|
sessionCapturer,
|
|
2293
2507
|
() => this.rowTest(),
|
|
@@ -2310,6 +2524,7 @@ var BrowserProxy = class {
|
|
|
2310
2524
|
* rather than appending, showing only the final execution result.
|
|
2311
2525
|
*/
|
|
2312
2526
|
retryTracker = new RetryTracker();
|
|
2527
|
+
waits;
|
|
2313
2528
|
/**
|
|
2314
2529
|
* Monotonic issue-order counter. Stamped when the test ISSUES a row — a
|
|
2315
2530
|
* driver command at invocation, an assert at enqueue — which is source order.
|
|
@@ -2327,6 +2542,8 @@ var BrowserProxy = class {
|
|
|
2327
2542
|
* the assert path, which runs outside any command callback.
|
|
2328
2543
|
*/
|
|
2329
2544
|
browser = null;
|
|
2545
|
+
/** The test core's value registry currently holds reads for; see {@link scopeReadValuesToRowTest}. */
|
|
2546
|
+
readValueTestUid;
|
|
2330
2547
|
/**
|
|
2331
2548
|
* The test a captured row belongs to: the `it` Nightwatch reports as running
|
|
2332
2549
|
* right now, falling back to the lifecycle test. On the BDD `describe/it`
|
|
@@ -2345,6 +2562,19 @@ var BrowserProxy = class {
|
|
|
2345
2562
|
rowTestUid() {
|
|
2346
2563
|
return this.rowTest()?.uid;
|
|
2347
2564
|
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Forget the values an earlier test read once the running test changes. The
|
|
2567
|
+
* lifecycle reset below fires once per MODULE on the BDD interface, so this
|
|
2568
|
+
* per-`it` live read is what keeps one `it`'s reads from naming another's
|
|
2569
|
+
* assertion target. Called before every record and every lookup.
|
|
2570
|
+
*/
|
|
2571
|
+
scopeReadValuesToRowTest() {
|
|
2572
|
+
const uid = this.rowTestUid();
|
|
2573
|
+
if (uid !== this.readValueTestUid) {
|
|
2574
|
+
clearReadValues();
|
|
2575
|
+
this.readValueTestUid = uid;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2348
2578
|
/** Per-command-scope dedup state only. Cucumber calls this per STEP, so it
|
|
2349
2579
|
* must not touch the native-assert buffer — that buffer spans the whole
|
|
2350
2580
|
* scenario and is drained once, at the scenario's pre-quit hook. */
|
|
@@ -2358,6 +2588,8 @@ var BrowserProxy = class {
|
|
|
2358
2588
|
resetTestTracking() {
|
|
2359
2589
|
this.resetCommandTracking();
|
|
2360
2590
|
this.assertRecorder.clear();
|
|
2591
|
+
clearReadValues();
|
|
2592
|
+
this.readValueTestUid = void 0;
|
|
2361
2593
|
}
|
|
2362
2594
|
/** Hand off this test's recorded native-assertion calls and clear the
|
|
2363
2595
|
* buffer so the next test starts fresh. */
|
|
@@ -2370,47 +2602,9 @@ var BrowserProxy = class {
|
|
|
2370
2602
|
setCurrentTestFullPath(path5) {
|
|
2371
2603
|
this.currentTestFullPath = path5;
|
|
2372
2604
|
}
|
|
2373
|
-
/**
|
|
2374
|
-
* Wrap browser navigation methods (url / navigate / navigateTo) to inject
|
|
2375
|
-
* the DevTools script after every navigation.
|
|
2376
|
-
*
|
|
2377
|
-
* Uses `browser` from the closure (not `this` inside perform) so it works
|
|
2378
|
-
* for both standard Nightwatch (chainable API) and Cucumber async/await mode
|
|
2379
|
-
* where `this` inside a perform callback is not the browser.
|
|
2380
|
-
*/
|
|
2605
|
+
/** Inject the DevTools script after every navigation; see {@link wrapNavigation}. */
|
|
2381
2606
|
wrapUrlMethod(browser) {
|
|
2382
|
-
|
|
2383
|
-
const b = browser;
|
|
2384
|
-
const wrapNav = (methodName) => {
|
|
2385
|
-
if (typeof b[methodName] !== "function") {
|
|
2386
|
-
return;
|
|
2387
|
-
}
|
|
2388
|
-
const original = b[methodName].bind(browser);
|
|
2389
|
-
b[methodName] = function(...args) {
|
|
2390
|
-
if (typeof args[0] !== "string") {
|
|
2391
|
-
return original(...args);
|
|
2392
|
-
}
|
|
2393
|
-
const result = original(...args);
|
|
2394
|
-
const injectAndCapture = () => {
|
|
2395
|
-
log7.info(`[nav] ${methodName}(${args[0] ?? ""}) \u2014 injecting script`);
|
|
2396
|
-
return sessionCapturer.injectScript(browser).then(() => sessionCapturer.captureTrace(browser, true)).catch(
|
|
2397
|
-
(err) => log7.error(`Failed to inject script: ${err.message}`)
|
|
2398
|
-
);
|
|
2399
|
-
};
|
|
2400
|
-
const chainable = result;
|
|
2401
|
-
if (chainable && typeof chainable.perform === "function") {
|
|
2402
|
-
void Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2403
|
-
});
|
|
2404
|
-
return result;
|
|
2405
|
-
}
|
|
2406
|
-
return Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2407
|
-
});
|
|
2408
|
-
};
|
|
2409
|
-
};
|
|
2410
|
-
wrapNav("url");
|
|
2411
|
-
wrapNav("navigate");
|
|
2412
|
-
wrapNav("navigateTo");
|
|
2413
|
-
log7.info("\u2713 Script injection wrapped");
|
|
2607
|
+
wrapNavigation(browser, this.sessionCapturer);
|
|
2414
2608
|
}
|
|
2415
2609
|
wrapBrowserCommands(browser) {
|
|
2416
2610
|
this.browser = browser;
|
|
@@ -2448,39 +2642,81 @@ var BrowserProxy = class {
|
|
|
2448
2642
|
this.assertRecorder.wrapNamespaces(browser);
|
|
2449
2643
|
}
|
|
2450
2644
|
this.proxiedBrowsers.add(browser);
|
|
2451
|
-
|
|
2645
|
+
log8.info(`\u2713 Wrapped ${wrappedMethods.length} browser methods`);
|
|
2452
2646
|
}
|
|
2453
|
-
handleRetryReplacement(browser, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2647
|
+
handleRetryReplacement(browser, oldId, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2454
2648
|
const { entry, oldTimestamp } = this.sessionCapturer.replaceCommand(
|
|
2455
|
-
|
|
2649
|
+
oldId,
|
|
2456
2650
|
methodName,
|
|
2457
2651
|
logArgs,
|
|
2458
2652
|
serializedResult,
|
|
2459
2653
|
commandError,
|
|
2460
2654
|
meta
|
|
2461
2655
|
);
|
|
2462
|
-
this.retryTracker.setLastId(entry._id ?? null);
|
|
2463
2656
|
this.sessionCapturer.sendReplaceCommand(oldTimestamp, entry);
|
|
2464
2657
|
this.attachScreenshot(browser, entry, methodName, " (retry)");
|
|
2658
|
+
return entry._id ?? null;
|
|
2465
2659
|
}
|
|
2466
|
-
captureFreshCommand(browser, methodName, logArgs, serializedResult, meta,
|
|
2467
|
-
this.retryTracker.setLastSig(cmdSig);
|
|
2468
|
-
this.retryTracker.setLastId(null);
|
|
2660
|
+
captureFreshCommand(browser, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2469
2661
|
this.drainOutgoingPage(browser, methodName);
|
|
2470
2662
|
this.sessionCapturer.captureCommand(methodName, logArgs, serializedResult, commandError, meta).catch(
|
|
2471
|
-
(err) =>
|
|
2663
|
+
(err) => log8.error(`Failed to capture ${methodName}: ${err.message}`)
|
|
2472
2664
|
);
|
|
2473
2665
|
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
2474
2666
|
if (lastCommand) {
|
|
2475
|
-
this.retryTracker.setLastId(lastCommand._id ?? null);
|
|
2476
2667
|
this.sessionCapturer.sendCommand(lastCommand);
|
|
2477
|
-
|
|
2668
|
+
log8.info(`[command] ${methodName}`);
|
|
2478
2669
|
this.attachScreenshot(browser, lastCommand, methodName);
|
|
2479
2670
|
}
|
|
2480
2671
|
this.anchorDestination(browser, methodName);
|
|
2481
2672
|
if (!this.isDomMutating(methodName)) {
|
|
2482
2673
|
void this.sessionCapturer.nativeNetwork.forward({ throttle: true });
|
|
2483
2674
|
}
|
|
2675
|
+
return lastCommand?._id ?? null;
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Record a completed command. A poll folds into the row of the previous
|
|
2679
|
+
* identical call: the last row captured, or, inside a wait, the row of the
|
|
2680
|
+
* same call its condition already issued, since a condition with two reads
|
|
2681
|
+
* alternates them and never repeats the last one.
|
|
2682
|
+
*/
|
|
2683
|
+
recordCompleted(browser, cmd, meta, serializedResult, commandError) {
|
|
2684
|
+
const { methodName, logArgs, cmdSig, parent } = cmd;
|
|
2685
|
+
const foldInto = parent ? parent.children.get(cmdSig) : this.retryTracker.isRetry(cmdSig) ? this.retryTracker.lastId : void 0;
|
|
2686
|
+
const id = foldInto === void 0 ? this.captureFreshCommand(
|
|
2687
|
+
browser,
|
|
2688
|
+
methodName,
|
|
2689
|
+
logArgs,
|
|
2690
|
+
serializedResult,
|
|
2691
|
+
meta,
|
|
2692
|
+
commandError
|
|
2693
|
+
) : this.handleRetryReplacement(
|
|
2694
|
+
browser,
|
|
2695
|
+
foldInto,
|
|
2696
|
+
methodName,
|
|
2697
|
+
logArgs,
|
|
2698
|
+
serializedResult,
|
|
2699
|
+
meta,
|
|
2700
|
+
commandError
|
|
2701
|
+
);
|
|
2702
|
+
if (!parent) {
|
|
2703
|
+
this.retryTracker.recordCapture(cmdSig, id);
|
|
2704
|
+
} else if (id !== null) {
|
|
2705
|
+
parent.children.set(cmdSig, id);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
/** Close the waits an aborted queue never ran, before the test unit's rows are read. */
|
|
2709
|
+
closeUnrunWaits() {
|
|
2710
|
+
if (this.waits.closeUnrun().length > 0) {
|
|
2711
|
+
this.retryTracker.reset();
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
/** End a wait's row; whatever is captured next is not a poll of what ran before it. */
|
|
2715
|
+
closeWait(browser, wait, result, error, completedAt) {
|
|
2716
|
+
this.retryTracker.reset();
|
|
2717
|
+
this.waits.close(wait, result, error, completedAt);
|
|
2718
|
+
this.attachScreenshot(browser, wait.row, wait.row.command);
|
|
2719
|
+
void this.sessionCapturer.nativeNetwork.forward({ throttle: true });
|
|
2484
2720
|
}
|
|
2485
2721
|
isDomMutating(methodName) {
|
|
2486
2722
|
return NAVIGATION_COMMANDS.includes(methodName) || [
|
|
@@ -2550,47 +2786,44 @@ var BrowserProxy = class {
|
|
|
2550
2786
|
* into one call because `makeCaptureCallback` is already over the line cap. */
|
|
2551
2787
|
serializeAndAttributeResult(methodName, logArgs, callbackResult) {
|
|
2552
2788
|
const result = serializeCommandResult(callbackResult, methodName);
|
|
2789
|
+
this.scopeReadValuesToRowTest();
|
|
2553
2790
|
rememberReadValue(result, commandTargetSelector(methodName, logArgs));
|
|
2554
2791
|
return result;
|
|
2555
2792
|
}
|
|
2556
2793
|
// Result-capturing callback factory — called by Nightwatch's async queue
|
|
2557
2794
|
// when the command completes. This is where we get the *actual* result.
|
|
2558
|
-
makeCaptureCallback(browser,
|
|
2795
|
+
makeCaptureCallback(browser, cmd) {
|
|
2796
|
+
const { methodName, logArgs, cmdSig, userCallback } = cmd;
|
|
2559
2797
|
return (callbackResult) => {
|
|
2560
2798
|
const commandTimestamp = Date.now();
|
|
2561
2799
|
this.popCommandStackIfMatches(methodName, cmdSig);
|
|
2562
2800
|
const commandError = callbackError(callbackResult);
|
|
2563
2801
|
const serializedResult = commandError ? void 0 : this.serializeAndAttributeResult(methodName, logArgs, callbackResult);
|
|
2564
|
-
const effectiveUid = this.rowTestUid() ?? testUid;
|
|
2565
|
-
if (
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
meta,
|
|
2590
|
-
cmdSig,
|
|
2591
|
-
commandError
|
|
2592
|
-
);
|
|
2593
|
-
}
|
|
2802
|
+
const effectiveUid = this.rowTestUid() ?? cmd.testUid;
|
|
2803
|
+
if (cmd.wait) {
|
|
2804
|
+
this.closeWait(
|
|
2805
|
+
browser,
|
|
2806
|
+
cmd.wait,
|
|
2807
|
+
serializedResult,
|
|
2808
|
+
commandError,
|
|
2809
|
+
commandTimestamp
|
|
2810
|
+
);
|
|
2811
|
+
} else if (effectiveUid && cmd.hasUserSource) {
|
|
2812
|
+
this.recordCompleted(
|
|
2813
|
+
browser,
|
|
2814
|
+
cmd,
|
|
2815
|
+
{
|
|
2816
|
+
testUid: effectiveUid,
|
|
2817
|
+
callSource: cmd.callSource,
|
|
2818
|
+
timestamp: commandTimestamp,
|
|
2819
|
+
startTime: cmd.invokedAt,
|
|
2820
|
+
sequence: cmd.sequence,
|
|
2821
|
+
selector: commandRowSelector(methodName, logArgs),
|
|
2822
|
+
parentId: cmd.parent?.row.id
|
|
2823
|
+
},
|
|
2824
|
+
serializedResult,
|
|
2825
|
+
commandError
|
|
2826
|
+
);
|
|
2594
2827
|
}
|
|
2595
2828
|
if (userCallback) {
|
|
2596
2829
|
return userCallback(callbackResult);
|
|
@@ -2614,7 +2847,7 @@ var BrowserProxy = class {
|
|
|
2614
2847
|
this.currentTestFullPath = callInfo.filePath;
|
|
2615
2848
|
}
|
|
2616
2849
|
const lastArg = args[args.length - 1];
|
|
2617
|
-
const hasUserCallback = typeof lastArg === "function";
|
|
2850
|
+
const hasUserCallback = typeof lastArg === "function" && !(methodName === "waitUntil" && args.length === 1);
|
|
2618
2851
|
const logArgs = hasUserCallback ? args.slice(0, -1) : args;
|
|
2619
2852
|
const cmdSig = JSON.stringify({
|
|
2620
2853
|
command: methodName,
|
|
@@ -2637,49 +2870,59 @@ var BrowserProxy = class {
|
|
|
2637
2870
|
browserAny.currentTest
|
|
2638
2871
|
)
|
|
2639
2872
|
);
|
|
2640
|
-
const
|
|
2641
|
-
const
|
|
2642
|
-
const sequence = this.issueCounter++;
|
|
2643
|
-
const captureCallback = this.makeCaptureCallback(
|
|
2644
|
-
browser,
|
|
2645
|
-
methodName,
|
|
2646
|
-
logArgs,
|
|
2647
|
-
cmdSig,
|
|
2648
|
-
callSource,
|
|
2649
|
-
hasUserSource,
|
|
2650
|
-
invokedAt,
|
|
2651
|
-
sequence,
|
|
2652
|
-
this.rowTestUid(),
|
|
2653
|
-
userCallback
|
|
2654
|
-
);
|
|
2873
|
+
const cmd = this.pendingCommand(methodName, args);
|
|
2874
|
+
const captureCallback = this.makeCaptureCallback(browser, cmd);
|
|
2655
2875
|
const closeInputWindow = beginInputDispatch(methodName);
|
|
2656
2876
|
const gatedCallback = (callbackResult) => {
|
|
2657
2877
|
closeInputWindow();
|
|
2658
2878
|
return captureCallback(callbackResult);
|
|
2659
2879
|
};
|
|
2660
|
-
const modifiedArgs = [...
|
|
2880
|
+
const modifiedArgs = [...this.waits.args(cmd), gatedCallback];
|
|
2661
2881
|
try {
|
|
2662
2882
|
return originalMethod(...modifiedArgs);
|
|
2663
2883
|
} catch (error) {
|
|
2664
2884
|
closeInputWindow();
|
|
2665
|
-
this.popCommandStackIfMatches(methodName, cmdSig);
|
|
2666
|
-
|
|
2885
|
+
this.popCommandStackIfMatches(methodName, cmd.cmdSig);
|
|
2886
|
+
if (cmd.wait) {
|
|
2887
|
+
this.closeWait(browser, cmd.wait, void 0, toError(error), Date.now());
|
|
2888
|
+
} else {
|
|
2889
|
+
this.captureCommandError(methodName, cmd.logArgs, error, cmd.callSource);
|
|
2890
|
+
}
|
|
2667
2891
|
throw error;
|
|
2668
2892
|
}
|
|
2669
2893
|
}
|
|
2894
|
+
/** Describe the call, and send its row now when it is a wait. */
|
|
2895
|
+
pendingCommand(methodName, args) {
|
|
2896
|
+
const cmd = {
|
|
2897
|
+
...this.describeInvocation(methodName, args),
|
|
2898
|
+
methodName,
|
|
2899
|
+
// Invocation time only: the row's real completion stamp is taken inside
|
|
2900
|
+
// the capture callback (see makeCaptureCallback).
|
|
2901
|
+
invokedAt: Date.now(),
|
|
2902
|
+
sequence: this.issueCounter++,
|
|
2903
|
+
testUid: this.rowTestUid(),
|
|
2904
|
+
parent: this.waits.enclosing()
|
|
2905
|
+
};
|
|
2906
|
+
if (isWaitCommand(methodName) && cmd.testUid && cmd.hasUserSource) {
|
|
2907
|
+
this.retryTracker.reset();
|
|
2908
|
+
cmd.wait = this.waits.open(cmd);
|
|
2909
|
+
log8.info(`[command] ${methodName} (running)`);
|
|
2910
|
+
}
|
|
2911
|
+
return cmd;
|
|
2912
|
+
}
|
|
2670
2913
|
captureCommandError(methodName, args, error, callSource) {
|
|
2671
2914
|
const currentTest = this.rowTest();
|
|
2672
2915
|
if (!currentTest) {
|
|
2673
2916
|
return;
|
|
2674
2917
|
}
|
|
2675
2918
|
const normalizedError = toError(error);
|
|
2676
|
-
|
|
2919
|
+
log8.error(`[command error] ${methodName}: ${normalizedError.message}`);
|
|
2677
2920
|
this.sessionCapturer.captureCommand(methodName, args, void 0, normalizedError, {
|
|
2678
2921
|
testUid: currentTest.uid,
|
|
2679
2922
|
callSource,
|
|
2680
2923
|
selector: commandRowSelector(methodName, args)
|
|
2681
2924
|
}).catch(
|
|
2682
|
-
(err) =>
|
|
2925
|
+
(err) => log8.error(`Failed to capture ${methodName}: ${err.message}`)
|
|
2683
2926
|
);
|
|
2684
2927
|
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
2685
2928
|
if (lastCommand) {
|
|
@@ -2699,10 +2942,10 @@ var BrowserProxy = class {
|
|
|
2699
2942
|
attachScreenshot(browser, entry, methodName, suffix = "") {
|
|
2700
2943
|
const ts = entry.timestamp ?? 0;
|
|
2701
2944
|
this.sessionCapturer.takeScreenshotViaHttp(browser).then((screenshot) => {
|
|
2702
|
-
if (screenshot) {
|
|
2945
|
+
if (screenshot && this.sessionCapturer.commandsLog.includes(entry)) {
|
|
2703
2946
|
entry.screenshot = screenshot;
|
|
2704
2947
|
this.sessionCapturer.sendReplaceCommand(ts, entry);
|
|
2705
|
-
|
|
2948
|
+
log8.info(`[screenshot] Attached to ${methodName}${suffix}`);
|
|
2706
2949
|
}
|
|
2707
2950
|
}).catch(() => {
|
|
2708
2951
|
});
|
|
@@ -2710,11 +2953,11 @@ var BrowserProxy = class {
|
|
|
2710
2953
|
};
|
|
2711
2954
|
|
|
2712
2955
|
// src/screencast-session.ts
|
|
2713
|
-
import
|
|
2956
|
+
import logger10 from "@wdio/logger";
|
|
2714
2957
|
|
|
2715
2958
|
// src/screencast.ts
|
|
2716
|
-
import
|
|
2717
|
-
var
|
|
2959
|
+
import logger9 from "@wdio/logger";
|
|
2960
|
+
var log9 = logger9("@wdio/nightwatch-devtools:ScreencastRecorder");
|
|
2718
2961
|
var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
2719
2962
|
#sessionCapturer;
|
|
2720
2963
|
constructor(sessionCapturer, options) {
|
|
@@ -2722,15 +2965,15 @@ var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
|
2722
2965
|
this.#sessionCapturer = sessionCapturer;
|
|
2723
2966
|
}
|
|
2724
2967
|
onPollingStarted(intervalMs) {
|
|
2725
|
-
|
|
2968
|
+
log9.info(
|
|
2726
2969
|
`\u2713 Screencast recording started (polling mode, ${intervalMs} ms interval)`
|
|
2727
2970
|
);
|
|
2728
2971
|
}
|
|
2729
2972
|
onPollingStopped(frameCount) {
|
|
2730
|
-
|
|
2973
|
+
log9.info(`\u2713 Screencast stopped \u2014 ${frameCount} frame(s) collected`);
|
|
2731
2974
|
}
|
|
2732
2975
|
onUnavailable(err) {
|
|
2733
|
-
|
|
2976
|
+
log9.warn(
|
|
2734
2977
|
`Screencast unavailable (${errorMessage(err)}). Recording skipped.`
|
|
2735
2978
|
);
|
|
2736
2979
|
}
|
|
@@ -2744,7 +2987,7 @@ var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
|
2744
2987
|
};
|
|
2745
2988
|
|
|
2746
2989
|
// src/screencast-session.ts
|
|
2747
|
-
var
|
|
2990
|
+
var log10 = logger10("@wdio/nightwatch-devtools:screencast-session");
|
|
2748
2991
|
async function startScreencast(ctx, browser, sessionId2) {
|
|
2749
2992
|
if (!ctx.screencastOptions.enabled || ctx.screencastRecorder || !sessionId2) {
|
|
2750
2993
|
return;
|
|
@@ -2754,7 +2997,7 @@ async function startScreencast(ctx, browser, sessionId2) {
|
|
|
2754
2997
|
ctx.screencastOptions
|
|
2755
2998
|
);
|
|
2756
2999
|
ctx.screencastSessionId = sessionId2;
|
|
2757
|
-
|
|
3000
|
+
log10.info(`\u{1F3AC} Starting screencast for session ${sessionId2}`);
|
|
2758
3001
|
await ctx.screencastRecorder.start(browser);
|
|
2759
3002
|
}
|
|
2760
3003
|
function needsScreencastRotation(ctx, sessionId2) {
|
|
@@ -2768,16 +3011,16 @@ function rotateScreencastForSession(ctx, browser) {
|
|
|
2768
3011
|
if (!needsScreencastRotation(ctx, sessionId2)) {
|
|
2769
3012
|
return;
|
|
2770
3013
|
}
|
|
2771
|
-
|
|
3014
|
+
log10.info(`\u{1F3AC} Session replaced \u2014 rotating screencast to ${sessionId2}`);
|
|
2772
3015
|
ctx.screencastRotation = ctx.finalizeCurrentScreencast().then(() => startScreencast(ctx, browser, sessionId2)).catch(
|
|
2773
|
-
(err) =>
|
|
3016
|
+
(err) => log10.warn(`Screencast rotation failed: ${errorMessage(err)}`)
|
|
2774
3017
|
).finally(() => {
|
|
2775
3018
|
ctx.screencastRotation = void 0;
|
|
2776
3019
|
});
|
|
2777
3020
|
}
|
|
2778
3021
|
|
|
2779
3022
|
// src/session-init.ts
|
|
2780
|
-
var
|
|
3023
|
+
var log11 = logger11("@wdio/nightwatch-devtools:session-init");
|
|
2781
3024
|
function initReporterChain(ctx) {
|
|
2782
3025
|
ctx.testReporter = new TestReporter(
|
|
2783
3026
|
(suitesData) => {
|
|
@@ -2811,7 +3054,7 @@ function readViewport(browser) {
|
|
|
2811
3054
|
return resolveViewport(browser.capabilities || {}, {
|
|
2812
3055
|
runScript: (body) => webdriverExecute2(browser, body),
|
|
2813
3056
|
getWindowSize: () => webdriverGet2(browser, "window/rect"),
|
|
2814
|
-
onWarn: (message) =>
|
|
3057
|
+
onWarn: (message) => log11.warn(message)
|
|
2815
3058
|
});
|
|
2816
3059
|
}
|
|
2817
3060
|
function wantsPerformanceLog(capabilities, desiredCapabilities) {
|
|
@@ -2844,7 +3087,7 @@ async function broadcastSessionMetadata(ctx, browser) {
|
|
|
2844
3087
|
ctx.sessionCapturer.sendUpstream("metadata", metadata);
|
|
2845
3088
|
const browserName = capabilities.browserName || desiredCapabilities.browserName || "unknown";
|
|
2846
3089
|
const browserVersion = capabilities.browserVersion || capabilities.version || "";
|
|
2847
|
-
|
|
3090
|
+
log11.info(
|
|
2848
3091
|
`\u2713 Browser: ${browserName}${browserVersion ? " " + browserVersion : ""} (session: ${sessionId2})`
|
|
2849
3092
|
);
|
|
2850
3093
|
if (ctx.mode === "live" && restoreBrowserFocus({
|
|
@@ -2853,9 +3096,9 @@ async function broadcastSessionMetadata(ctx, browser) {
|
|
|
2853
3096
|
capabilities.platformName || desiredCapabilities.platformName || ""
|
|
2854
3097
|
),
|
|
2855
3098
|
host: resolveWebDriverAddress(browser).driverHost,
|
|
2856
|
-
onError: (message) =>
|
|
3099
|
+
onError: (message) => log11.warn(message)
|
|
2857
3100
|
})) {
|
|
2858
|
-
|
|
3101
|
+
log11.info(
|
|
2859
3102
|
"\u2713 Safari brought back to the front (it delivers clicks only there)"
|
|
2860
3103
|
);
|
|
2861
3104
|
}
|
|
@@ -2865,7 +3108,7 @@ async function broadcastSessionMetadata(ctx, browser) {
|
|
|
2865
3108
|
);
|
|
2866
3109
|
ctx.sessionCapturer.perfLogsRequested = wantsPerfLog;
|
|
2867
3110
|
if (!wantsPerfLog && !ctx.bidiEnabled) {
|
|
2868
|
-
|
|
3111
|
+
log11.warn(
|
|
2869
3112
|
"\u26A0 Network tab will carry the page's own Resource Timing only (no headers, no bodies). Add 'goog:loggingPrefs': { performance: 'ALL' } to your capabilities (or enable bidi:true) for full requests"
|
|
2870
3113
|
);
|
|
2871
3114
|
}
|
|
@@ -2876,17 +3119,17 @@ async function tryAttachBidi(ctx, browser) {
|
|
|
2876
3119
|
}
|
|
2877
3120
|
const driver = browser.driver;
|
|
2878
3121
|
if (!driver) {
|
|
2879
|
-
|
|
3122
|
+
log11.warn("bidi:true set but browser.driver unavailable \u2014 skipping");
|
|
2880
3123
|
return;
|
|
2881
3124
|
}
|
|
2882
|
-
const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-
|
|
3125
|
+
const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-C67DYUCA.js");
|
|
2883
3126
|
const ok = await attachBidiHandlers(
|
|
2884
3127
|
driver,
|
|
2885
3128
|
buildBidiSinks(ctx.sessionCapturer)
|
|
2886
3129
|
);
|
|
2887
3130
|
if (ok) {
|
|
2888
3131
|
ctx.sessionCapturer.bidiActive = true;
|
|
2889
|
-
|
|
3132
|
+
log11.info("\u2713 BiDi attached \u2014 perf-log network capture disabled");
|
|
2890
3133
|
}
|
|
2891
3134
|
}
|
|
2892
3135
|
async function tryRegisterPreload(ctx, browser) {
|
|
@@ -2896,7 +3139,7 @@ async function tryRegisterPreload(ctx, browser) {
|
|
|
2896
3139
|
}
|
|
2897
3140
|
ctx.sessionCapturer.preloadRegistered = await registerCollectorPreload(
|
|
2898
3141
|
driver,
|
|
2899
|
-
(level, message) =>
|
|
3142
|
+
(level, message) => log11[level](message)
|
|
2900
3143
|
);
|
|
2901
3144
|
}
|
|
2902
3145
|
async function armCaptureForSession(ctx, browser) {
|
|
@@ -2923,16 +3166,16 @@ async function armReplacedSession(ctx, browser) {
|
|
|
2923
3166
|
ctx.sessionCapturer.bidiNetworkObserved = false;
|
|
2924
3167
|
ctx.sessionCapturer.loadsListedByLog = [];
|
|
2925
3168
|
ctx.sessionCapturer.nativeNetwork.stop();
|
|
2926
|
-
|
|
3169
|
+
log11.info(`Session replaced \u2014 re-arming capture for ${sessionId2}`);
|
|
2927
3170
|
await armCaptureForSession(ctx, browser);
|
|
2928
3171
|
}
|
|
2929
3172
|
function rearmCaptureForSession(ctx, browser) {
|
|
2930
3173
|
void armReplacedSession(ctx, browser).catch(
|
|
2931
|
-
(err) =>
|
|
3174
|
+
(err) => log11.warn(`Capture re-arm failed: ${errorMessage(err)}`)
|
|
2932
3175
|
);
|
|
2933
3176
|
}
|
|
2934
3177
|
async function rebindSessionToBrowser(ctx, browser) {
|
|
2935
|
-
|
|
3178
|
+
log11.info("Browser session changed \u2014 re-targeting the run capturer");
|
|
2936
3179
|
ctx.isScriptInjected = false;
|
|
2937
3180
|
await broadcastSessionMetadata(ctx, browser);
|
|
2938
3181
|
await armReplacedSession(ctx, browser);
|
|
@@ -2962,7 +3205,7 @@ async function ensureSessionInitialized(ctx, browser) {
|
|
|
2962
3205
|
if (ctx.mode !== "trace") {
|
|
2963
3206
|
const connected = await ctx.sessionCapturer.waitForConnection(3e3);
|
|
2964
3207
|
if (!connected) {
|
|
2965
|
-
|
|
3208
|
+
log11.error("\u274C Worker WebSocket failed to connect!");
|
|
2966
3209
|
}
|
|
2967
3210
|
}
|
|
2968
3211
|
initReporterChain(ctx);
|
|
@@ -2987,7 +3230,7 @@ async function finalizeCurrentScreencast(ctx) {
|
|
|
2987
3230
|
}),
|
|
2988
3231
|
captureFormat: ctx.screencastOptions.captureFormat,
|
|
2989
3232
|
sendUpstream: (scope, data) => ctx.sessionCapturer?.sendUpstream(scope, data),
|
|
2990
|
-
onLog: (level, message) =>
|
|
3233
|
+
onLog: (level, message) => log11[level](message)
|
|
2991
3234
|
});
|
|
2992
3235
|
}
|
|
2993
3236
|
ctx.screencastRecorder = void 0;
|
|
@@ -3045,8 +3288,8 @@ var PluginScreencast = class {
|
|
|
3045
3288
|
};
|
|
3046
3289
|
|
|
3047
3290
|
// src/event-hub.ts
|
|
3048
|
-
import
|
|
3049
|
-
var
|
|
3291
|
+
import logger12 from "@wdio/logger";
|
|
3292
|
+
var log12 = logger12("@wdio/nightwatch-devtools:event-hub");
|
|
3050
3293
|
function makeSessionMetadataHandler(bindings) {
|
|
3051
3294
|
return (data) => {
|
|
3052
3295
|
try {
|
|
@@ -3067,23 +3310,23 @@ function makeSessionMetadataHandler(bindings) {
|
|
|
3067
3310
|
options: bindings.buildMetadataOptions()
|
|
3068
3311
|
});
|
|
3069
3312
|
} catch (err) {
|
|
3070
|
-
|
|
3313
|
+
log12.error(`Error in event handler: ${errorMessage(err)}`);
|
|
3071
3314
|
}
|
|
3072
3315
|
};
|
|
3073
3316
|
}
|
|
3074
3317
|
function registerEventHandlers(eventHub, bindings) {
|
|
3075
3318
|
bindings.setCucumberRunner(eventHub.runner === "cucumber");
|
|
3076
3319
|
if (eventHub.runner === "cucumber") {
|
|
3077
|
-
|
|
3320
|
+
log12.info("\u2713 Cucumber runner detected via NightwatchEventHub");
|
|
3078
3321
|
}
|
|
3079
|
-
|
|
3322
|
+
log12.info("\u2713 NightwatchEventHub registered \u2014 enriched metadata enabled");
|
|
3080
3323
|
const handler = makeSessionMetadataHandler(bindings);
|
|
3081
3324
|
eventHub.on("TestSuiteStarted", handler);
|
|
3082
3325
|
eventHub.on("TestRunStarted", handler);
|
|
3083
3326
|
}
|
|
3084
3327
|
|
|
3085
3328
|
// src/cucumber-lifecycle.ts
|
|
3086
|
-
import
|
|
3329
|
+
import logger13 from "@wdio/logger";
|
|
3087
3330
|
|
|
3088
3331
|
// src/helpers/cucumberResult.ts
|
|
3089
3332
|
function cucumberResultToTestState(result) {
|
|
@@ -3278,7 +3521,7 @@ function flushTestSlice(ctx) {
|
|
|
3278
3521
|
}
|
|
3279
3522
|
|
|
3280
3523
|
// src/cucumber-lifecycle.ts
|
|
3281
|
-
var
|
|
3524
|
+
var log13 = logger13("@wdio/nightwatch-devtools:cucumber");
|
|
3282
3525
|
function attachScenarioToFeature(ctx, featureSuite, scenarioSuite) {
|
|
3283
3526
|
const existingIdx = featureSuite.suites.findIndex(
|
|
3284
3527
|
(s) => s.uid === scenarioSuite.uid
|
|
@@ -3362,7 +3605,7 @@ async function initCucumberScenario(ctx, browser, pickle) {
|
|
|
3362
3605
|
ctx.setCurrentTest(null);
|
|
3363
3606
|
ctx.testReporter.updateSuites();
|
|
3364
3607
|
ctx.wrapBrowserOnce(browser);
|
|
3365
|
-
|
|
3608
|
+
log13.info(`\u{1F952} Scenario: ${scenarioName}`);
|
|
3366
3609
|
}
|
|
3367
3610
|
async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
3368
3611
|
try {
|
|
@@ -3384,7 +3627,7 @@ async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
|
3384
3627
|
}
|
|
3385
3628
|
ctx.incrementCount(scenarioState);
|
|
3386
3629
|
const icon = ctx.testIcon(scenarioState);
|
|
3387
|
-
|
|
3630
|
+
log13.info(
|
|
3388
3631
|
` ${icon} ${pickle?.name ?? "Unknown"} (${(duration / 1e3).toFixed(2)}s)`
|
|
3389
3632
|
);
|
|
3390
3633
|
ctx.testReporter.updateSuites();
|
|
@@ -3393,12 +3636,13 @@ async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
|
3393
3636
|
ctx.setCurrentTest(null);
|
|
3394
3637
|
}
|
|
3395
3638
|
} catch (err) {
|
|
3396
|
-
|
|
3639
|
+
log13.error(`Failed to finalize Cucumber scenario: ${errorMessage(err)}`);
|
|
3397
3640
|
}
|
|
3398
3641
|
}
|
|
3399
3642
|
async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
|
|
3400
3643
|
const scenario = ctx.getCurrentScenarioSuite();
|
|
3401
3644
|
try {
|
|
3645
|
+
ctx.browserProxy?.closeUnrunWaits();
|
|
3402
3646
|
const assertCalls = ctx.browserProxy?.drainNativeAssertCalls() ?? [];
|
|
3403
3647
|
if (assertCalls.length > 0) {
|
|
3404
3648
|
await captureNativeAssertions(
|
|
@@ -3416,7 +3660,7 @@ async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
|
|
|
3416
3660
|
cucumberResultToTestState(result) === TEST_STATE.FAILED
|
|
3417
3661
|
);
|
|
3418
3662
|
} catch (err) {
|
|
3419
|
-
|
|
3663
|
+
log13.error(
|
|
3420
3664
|
`Failed to capture Cucumber scenario before quit: ${errorMessage(err)}`
|
|
3421
3665
|
);
|
|
3422
3666
|
}
|
|
@@ -3462,7 +3706,7 @@ async function cucumberAfter(ctx, browser, result, pickle) {
|
|
|
3462
3706
|
}
|
|
3463
3707
|
|
|
3464
3708
|
// src/test-lifecycle.ts
|
|
3465
|
-
import
|
|
3709
|
+
import logger15 from "@wdio/logger";
|
|
3466
3710
|
|
|
3467
3711
|
// src/helpers/specFileResolver.ts
|
|
3468
3712
|
import fs4 from "fs";
|
|
@@ -3510,8 +3754,8 @@ function resolveSpecFilePath(testFile, modulePath, srcFolders, cachedPath) {
|
|
|
3510
3754
|
}
|
|
3511
3755
|
|
|
3512
3756
|
// src/helpers/closePreviousTest.ts
|
|
3513
|
-
import
|
|
3514
|
-
var
|
|
3757
|
+
import logger14 from "@wdio/logger";
|
|
3758
|
+
var log14 = logger14("@wdio/nightwatch-devtools:closePreviousTest");
|
|
3515
3759
|
async function closePreviousTest(input) {
|
|
3516
3760
|
const {
|
|
3517
3761
|
runningTest,
|
|
@@ -3530,7 +3774,7 @@ async function closePreviousTest(input) {
|
|
|
3530
3774
|
testManager.updateTestState(runningTest, testState);
|
|
3531
3775
|
testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3532
3776
|
incrementCount(testState);
|
|
3533
|
-
|
|
3777
|
+
log14.info(
|
|
3534
3778
|
` ${testIcon(testState)} ${runningTest.title} (${(runningTest._duration / 1e3).toFixed(2)}s)`
|
|
3535
3779
|
);
|
|
3536
3780
|
} else {
|
|
@@ -3544,13 +3788,13 @@ async function closePreviousTest(input) {
|
|
|
3544
3788
|
);
|
|
3545
3789
|
testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3546
3790
|
incrementCount(TEST_STATE.PASSED);
|
|
3547
|
-
|
|
3791
|
+
log14.info(` \u2705 ${runningTest.title} (${(duration / 1e3).toFixed(2)}s)`);
|
|
3548
3792
|
}
|
|
3549
3793
|
await new Promise((resolve2) => setTimeout(resolve2, TIMING.UI_RENDER_DELAY));
|
|
3550
3794
|
}
|
|
3551
3795
|
|
|
3552
3796
|
// src/test-lifecycle.ts
|
|
3553
|
-
var
|
|
3797
|
+
var log15 = logger15("@wdio/nightwatch-devtools:test-lifecycle");
|
|
3554
3798
|
function resolveSuiteMetadata(ctx, currentTest) {
|
|
3555
3799
|
const moduleName = currentTest.module ?? "";
|
|
3556
3800
|
const testFile = moduleName.split("/").pop() || moduleName || DEFAULTS.FILE_NAME;
|
|
@@ -3561,7 +3805,7 @@ function resolveSuiteMetadata(ctx, currentTest) {
|
|
|
3561
3805
|
ctx.browserProxy.getCurrentTestFullPath() || void 0
|
|
3562
3806
|
);
|
|
3563
3807
|
if (!fullPath) {
|
|
3564
|
-
|
|
3808
|
+
log15.warn(
|
|
3565
3809
|
`[beforeEach] Could not resolve file path for "${testFile}" \u2014 source view will be unavailable`
|
|
3566
3810
|
);
|
|
3567
3811
|
}
|
|
@@ -3606,10 +3850,10 @@ async function startNextTest(ctx, currentSuite, currentTestName, processedTests,
|
|
|
3606
3850
|
test.end = null;
|
|
3607
3851
|
ctx.testReporter.onTestStart(test);
|
|
3608
3852
|
ctx.setCurrentTest(test);
|
|
3609
|
-
|
|
3853
|
+
log15.info(` \u25B6 ${currentTestName}`);
|
|
3610
3854
|
await new Promise((resolve2) => setTimeout(resolve2, TIMING.TEST_START_DELAY));
|
|
3611
3855
|
} else {
|
|
3612
|
-
|
|
3856
|
+
log15.warn(
|
|
3613
3857
|
`Test "${currentTestName}" not found in suite "${currentSuite.title}"`
|
|
3614
3858
|
);
|
|
3615
3859
|
ctx.setCurrentTest(null);
|
|
@@ -3654,7 +3898,7 @@ function closeUnreportedRunningTest(ctx, currentSuite, testFile, results, proces
|
|
|
3654
3898
|
ctx.testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3655
3899
|
ctx.incrementCount(testState);
|
|
3656
3900
|
const icon = ctx.testIcon(testState);
|
|
3657
|
-
|
|
3901
|
+
log15.info(` ${icon} ${runningTest.title} (${(duration / 1e3).toFixed(2)}s)`);
|
|
3658
3902
|
}
|
|
3659
3903
|
async function closeReportedTestcases(ctx, currentSuite, testFile, testcases, processedTests) {
|
|
3660
3904
|
const testcaseNames = Object.keys(testcases);
|
|
@@ -3670,7 +3914,7 @@ async function closeReportedTestcases(ctx, currentSuite, testFile, testcases, pr
|
|
|
3670
3914
|
ctx.testManager.updateTestState(test, testState, /* @__PURE__ */ new Date(), dur);
|
|
3671
3915
|
ctx.incrementCount(testState);
|
|
3672
3916
|
const icon = ctx.testIcon(testState);
|
|
3673
|
-
|
|
3917
|
+
log15.info(` ${icon} ${currentTestName} (${(dur / 1e3).toFixed(2)}s)`);
|
|
3674
3918
|
}
|
|
3675
3919
|
ctx.testManager.markTestAsProcessed(testFile, currentTestName);
|
|
3676
3920
|
}
|
|
@@ -3712,7 +3956,7 @@ async function closeOutTestcases(ctx, browser) {
|
|
|
3712
3956
|
}
|
|
3713
3957
|
|
|
3714
3958
|
// src/index.ts
|
|
3715
|
-
var
|
|
3959
|
+
var log16 = logger16("@wdio/nightwatch-devtools");
|
|
3716
3960
|
var NightwatchDevToolsPlugin = class {
|
|
3717
3961
|
options;
|
|
3718
3962
|
sessionCapturer;
|
|
@@ -3766,7 +4010,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3766
4010
|
screencast: options.screencast
|
|
3767
4011
|
});
|
|
3768
4012
|
if (screencast.warning) {
|
|
3769
|
-
|
|
4013
|
+
log16.warn(screencast.warning);
|
|
3770
4014
|
}
|
|
3771
4015
|
this.options = {
|
|
3772
4016
|
port: options.port ?? 3e3,
|
|
@@ -3787,7 +4031,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3787
4031
|
};
|
|
3788
4032
|
const policyWarning = tracePolicyModeWarning(options.tracePolicy, mode);
|
|
3789
4033
|
if (policyWarning) {
|
|
3790
|
-
|
|
4034
|
+
log16.warn(policyWarning);
|
|
3791
4035
|
}
|
|
3792
4036
|
this.#screencast = new PluginScreencast(
|
|
3793
4037
|
screencast.options,
|
|
@@ -4008,7 +4252,8 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4008
4252
|
// Rows are tagged per-`it` (BrowserProxy.rowTestUid); `#currentTestUid`
|
|
4009
4253
|
// — the lifecycle test, which the BDD interface pins to the module's
|
|
4010
4254
|
// first `it` — is only the fallback, and stays the artifact/slice key.
|
|
4011
|
-
() => this.browserProxy?.rowTestUid() ?? this.#currentTestUid()
|
|
4255
|
+
() => this.browserProxy?.rowTestUid() ?? this.#currentTestUid(),
|
|
4256
|
+
() => this.browserProxy?.scopeReadValuesToRowTest()
|
|
4012
4257
|
);
|
|
4013
4258
|
}
|
|
4014
4259
|
}
|
|
@@ -4120,6 +4365,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4120
4365
|
}
|
|
4121
4366
|
if (browser && this.sessionCapturer) {
|
|
4122
4367
|
try {
|
|
4368
|
+
this.browserProxy.closeUnrunWaits();
|
|
4123
4369
|
await this.#closeOutTestcases(browser);
|
|
4124
4370
|
if (this.options.captureAssertions) {
|
|
4125
4371
|
await captureNativeAssertions(
|
|
@@ -4136,7 +4382,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4136
4382
|
const failed = !!results && ((results.errors ?? 0) > 0 || (results.failed ?? 0) > 0);
|
|
4137
4383
|
await this.#emitTestArtifacts(this.#currentTestUid(), failed);
|
|
4138
4384
|
} catch (err) {
|
|
4139
|
-
|
|
4385
|
+
log16.error(`Failed to capture trace: ${errorMessage(err)}`);
|
|
4140
4386
|
}
|
|
4141
4387
|
}
|
|
4142
4388
|
}
|
|
@@ -4172,11 +4418,12 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4172
4418
|
outputDir: this.#outputDir,
|
|
4173
4419
|
captureFormat: this.#screencast.options.captureFormat,
|
|
4174
4420
|
onArtifact: (a) => this.#artifacts.push(a),
|
|
4175
|
-
onLog: (level, msg) =>
|
|
4421
|
+
onLog: (level, msg) => log16[level](msg)
|
|
4176
4422
|
});
|
|
4177
4423
|
}
|
|
4178
4424
|
async after(browser) {
|
|
4179
4425
|
this.sessionCapturer?.nativeNetwork.stop();
|
|
4426
|
+
this.browserProxy?.closeUnrunWaits();
|
|
4180
4427
|
await this.#screencast.rotation;
|
|
4181
4428
|
await this.#finalizeCurrentScreencast();
|
|
4182
4429
|
try {
|
|
@@ -4190,14 +4437,14 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4190
4437
|
}
|
|
4191
4438
|
if (!this.#devtoolsBrowser) {
|
|
4192
4439
|
this.testReporter?.updateSuites();
|
|
4193
|
-
|
|
4440
|
+
log16.info("\u267B Rerun complete \u2014 flushing WebSocket...");
|
|
4194
4441
|
await this.sessionCapturer?.closeWebSocket();
|
|
4195
4442
|
return;
|
|
4196
4443
|
}
|
|
4197
|
-
|
|
4444
|
+
log16.info("\u{1F4A1} Please close the DevTools browser window to finish...");
|
|
4198
4445
|
await this.#waitForDevtoolsBrowserClose();
|
|
4199
4446
|
} catch (err) {
|
|
4200
|
-
|
|
4447
|
+
log16.error(`Failed to stop backend: ${errorMessage(err)}`);
|
|
4201
4448
|
}
|
|
4202
4449
|
}
|
|
4203
4450
|
async #finalizeAllSuites(browser) {
|
|
@@ -4238,7 +4485,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4238
4485
|
screencastFrames: this.options.filmstrip ? this.#screencast.frames : void 0,
|
|
4239
4486
|
configPath: this.#configPath,
|
|
4240
4487
|
testFilePath: this.browserProxy?.getCurrentTestFullPath?.() ?? void 0,
|
|
4241
|
-
log: (level, msg) =>
|
|
4488
|
+
log: (level, msg) => log16[level](msg)
|
|
4242
4489
|
},
|
|
4243
4490
|
sessionId2
|
|
4244
4491
|
);
|