@wdio/nightwatch-devtools 1.8.2 → 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 +78 -16
- package/dist/{bidi-WNZOXX5X.js → bidi-C67DYUCA.js} +4 -2
- package/dist/{bidi-WNZOXX5X.js.map → bidi-C67DYUCA.js.map} +1 -1
- package/dist/{chunk-DM4FCAIJ.js → chunk-DRZC45YG.js} +961 -251
- package/dist/chunk-DRZC45YG.js.map +1 -0
- package/dist/index.js +727 -285
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/dist/chunk-DM4FCAIJ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CAPTURE_PERFORMANCE_SCRIPT,
|
|
3
|
-
|
|
3
|
+
COLLECTOR_INJECT_SCRIPT,
|
|
4
|
+
COLLECTOR_READY_SCRIPT,
|
|
5
|
+
NETWORK_CAPTURE_SOURCE,
|
|
6
|
+
NativeNetworkCapture,
|
|
4
7
|
REUSE_ENV,
|
|
8
|
+
RUNNER_ENV,
|
|
5
9
|
RetryTracker,
|
|
6
10
|
SCREENCAST_DEFAULTS,
|
|
7
11
|
ScreencastRecorderBase,
|
|
@@ -16,14 +20,17 @@ import {
|
|
|
16
20
|
captureActionSnapshot,
|
|
17
21
|
captureAndAttachScreenshot,
|
|
18
22
|
captureAndAttachVideo,
|
|
23
|
+
clearReadValues,
|
|
19
24
|
collectSuiteTestMetadata,
|
|
20
25
|
collectorDrainExpression,
|
|
26
|
+
commandFailure,
|
|
21
27
|
computeSuiteFinalStateStrict,
|
|
22
28
|
computeSuiteRunningState,
|
|
23
29
|
createSuiteStats,
|
|
24
30
|
createTestStats,
|
|
25
31
|
deterministicUid,
|
|
26
32
|
drainCollectorWithRecovery,
|
|
33
|
+
encodeInitialArgs,
|
|
27
34
|
errorMessage,
|
|
28
35
|
finalizeScreencast,
|
|
29
36
|
finalizeTraceExport,
|
|
@@ -35,9 +42,9 @@ import {
|
|
|
35
42
|
getCallSourceFromStack,
|
|
36
43
|
getRequestType,
|
|
37
44
|
isUserCodeFrame,
|
|
45
|
+
isWaitCommand,
|
|
38
46
|
lastRenderedScreenshot,
|
|
39
47
|
loadInjectableScript,
|
|
40
|
-
mapChromeBrowserLogs,
|
|
41
48
|
mapCommandToAction,
|
|
42
49
|
matcherAssertionToCommandLog,
|
|
43
50
|
normalizeFilePath,
|
|
@@ -47,9 +54,11 @@ import {
|
|
|
47
54
|
recordSpecBoundary,
|
|
48
55
|
registerCollectorPreload,
|
|
49
56
|
rememberReadValue,
|
|
57
|
+
requestsInterceptedNetwork,
|
|
50
58
|
resolveAdapterOutputDir,
|
|
51
59
|
resolveAssertTargetFromArgs,
|
|
52
60
|
resolveViewport,
|
|
61
|
+
restoreBrowserFocus,
|
|
53
62
|
safeSerializeAssertArg,
|
|
54
63
|
serializeError,
|
|
55
64
|
stampSuiteEnd,
|
|
@@ -58,9 +67,10 @@ import {
|
|
|
58
67
|
toError,
|
|
59
68
|
tracePolicyModeWarning,
|
|
60
69
|
upsertRichestSnapshot,
|
|
70
|
+
webdriverExecute,
|
|
61
71
|
webdriverGet,
|
|
62
72
|
webdriverPost
|
|
63
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-DRZC45YG.js";
|
|
64
74
|
|
|
65
75
|
// src/index.ts
|
|
66
76
|
import { fileURLToPath } from "url";
|
|
@@ -164,14 +174,18 @@ async function emitTestArtifacts(input) {
|
|
|
164
174
|
// src/helpers/assertCapture.ts
|
|
165
175
|
import logger from "@wdio/logger";
|
|
166
176
|
var log = logger("@wdio/nightwatch-devtools:assertCapture");
|
|
167
|
-
function wireAssertCapture(getCapturer, getTestUid) {
|
|
177
|
+
function wireAssertCapture(getCapturer, getTestUid, scopeReadValues = () => {
|
|
178
|
+
}) {
|
|
168
179
|
patchNodeAssert(
|
|
169
180
|
(cmd) => captureAssert(getCapturer(), getTestUid(), cmd),
|
|
170
181
|
(level, message) => log[level](message),
|
|
171
182
|
// No handle resolver: Nightwatch commands take selector strings, so an
|
|
172
183
|
// element object never reaches an assert — only the value's provenance,
|
|
173
184
|
// recorded by the command path (BrowserProxy), can name the target.
|
|
174
|
-
(args) =>
|
|
185
|
+
(args) => {
|
|
186
|
+
scopeReadValues();
|
|
187
|
+
return resolveAssertTargetFromArgs(args);
|
|
188
|
+
}
|
|
175
189
|
);
|
|
176
190
|
}
|
|
177
191
|
function captureAssert(capturer, testUid, cmd) {
|
|
@@ -196,7 +210,7 @@ function captureAssert(capturer, testUid, cmd) {
|
|
|
196
210
|
|
|
197
211
|
// src/index.ts
|
|
198
212
|
import { stop as stopBackend } from "@wdio/devtools-backend";
|
|
199
|
-
import
|
|
213
|
+
import logger16 from "@wdio/logger";
|
|
200
214
|
|
|
201
215
|
// src/run-lifecycle.ts
|
|
202
216
|
import * as fs2 from "fs";
|
|
@@ -253,6 +267,7 @@ var TIMING = {
|
|
|
253
267
|
};
|
|
254
268
|
var BOOLEAN_COMMAND_PATTERN = /^waitFor|^is[A-Z]|^has[A-Z]|(Visible|Present|Enabled|Selected|NotVisible|NotPresent)$/;
|
|
255
269
|
var NAVIGATION_COMMANDS = ["url", "navigate", "navigateTo"];
|
|
270
|
+
var NIGHTWATCH_CONFIG_FLAGS = ["--config", "-c"];
|
|
256
271
|
var CONFIG_FILENAMES = [
|
|
257
272
|
"nightwatch.conf.cjs",
|
|
258
273
|
"nightwatch.conf.js",
|
|
@@ -266,10 +281,13 @@ import * as fs from "fs";
|
|
|
266
281
|
import * as path from "path";
|
|
267
282
|
import { parse as parseStackTrace } from "stacktrace-parser";
|
|
268
283
|
function determineTestState(testcase) {
|
|
269
|
-
if (testcase.
|
|
284
|
+
if (testcase.failed > 0 || testcase.errors > 0) {
|
|
285
|
+
return "failed";
|
|
286
|
+
}
|
|
287
|
+
if (testcase.passed === 0 && testcase.skipped > 0) {
|
|
270
288
|
return "skipped";
|
|
271
289
|
}
|
|
272
|
-
return
|
|
290
|
+
return "passed";
|
|
273
291
|
}
|
|
274
292
|
function getTestIcon(state) {
|
|
275
293
|
return state === "passed" ? "\u2705" : state === "skipped" ? "\u23ED" : "\u274C";
|
|
@@ -290,13 +308,53 @@ function buildPluginMetadataOptions(input) {
|
|
|
290
308
|
return {
|
|
291
309
|
framework: nightwatchRunnerId(input.isCucumberRunner),
|
|
292
310
|
configFile: input.configPath,
|
|
293
|
-
baseDir: process.cwd()
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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;
|
|
299
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;
|
|
300
358
|
}
|
|
301
359
|
function generateStableUid2(itemOrFile, name) {
|
|
302
360
|
let file, testName;
|
|
@@ -517,6 +575,13 @@ async function runPluginBefore(ctx) {
|
|
|
517
575
|
ctx.options.port,
|
|
518
576
|
ctx.options.hostname
|
|
519
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();
|
|
520
585
|
log2.info("\u{1F680} Starting DevTools backend...");
|
|
521
586
|
const { port } = await start(ctx.options);
|
|
522
587
|
ctx.options.port = port;
|
|
@@ -634,7 +699,7 @@ async function waitForDevtoolsBrowserClose(ctx) {
|
|
|
634
699
|
}
|
|
635
700
|
|
|
636
701
|
// src/session-init.ts
|
|
637
|
-
import
|
|
702
|
+
import logger11 from "@wdio/logger";
|
|
638
703
|
|
|
639
704
|
// src/helpers/webdriverHttp.ts
|
|
640
705
|
import logger3 from "@wdio/logger";
|
|
@@ -654,7 +719,7 @@ var firstDefined = (obj, ...keys) => {
|
|
|
654
719
|
}
|
|
655
720
|
return void 0;
|
|
656
721
|
};
|
|
657
|
-
function
|
|
722
|
+
function configuredWebDriver(browser) {
|
|
658
723
|
const transportSettings = getPath(browser, "transport", "settings", "webdriver") || getPath(browser, "queue", "transport", "settings", "webdriver") || getPath(
|
|
659
724
|
browser,
|
|
660
725
|
"nightwatchInstance",
|
|
@@ -664,22 +729,47 @@ function resolveWebDriverAddress(browser) {
|
|
|
664
729
|
) || {};
|
|
665
730
|
const opts = getProp(browser, "options") ?? {};
|
|
666
731
|
const nightwatchSettings = getPath(browser, "nightwatchInstance", "settings") || getPath(browser, "globals", "nightwatchInstance", "settings") || {};
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
);
|
|
673
|
-
|
|
732
|
+
const sources = [
|
|
733
|
+
transportSettings,
|
|
734
|
+
getProp(opts, "webdriver"),
|
|
735
|
+
getProp(nightwatchSettings, "webdriver")
|
|
736
|
+
];
|
|
737
|
+
const values = (...keys) => sources.map((source) => firstDefined(source, ...keys));
|
|
738
|
+
const pick = (...keys) => values(...keys).find(Boolean);
|
|
739
|
+
return {
|
|
740
|
+
host: pick("host", "server_address"),
|
|
741
|
+
port: pick("port"),
|
|
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
|
+
};
|
|
674
748
|
}
|
|
675
|
-
function
|
|
676
|
-
const {
|
|
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;
|
|
677
753
|
return {
|
|
678
|
-
hostname
|
|
679
|
-
port
|
|
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) } : {},
|
|
680
759
|
onWarn: (message) => log3.warn(message)
|
|
681
760
|
};
|
|
682
761
|
}
|
|
762
|
+
function resolveWebDriverAddress(browser) {
|
|
763
|
+
const { host, port } = configuredWebDriver(browser);
|
|
764
|
+
return {
|
|
765
|
+
driverHost: String(host || "localhost"),
|
|
766
|
+
driverPort: Number(port || 9515)
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function address(browser) {
|
|
770
|
+
const { driverHost, driverPort } = resolveWebDriverAddress(browser);
|
|
771
|
+
return connection(configuredWebDriver(browser), driverHost, driverPort);
|
|
772
|
+
}
|
|
683
773
|
function sessionId(browser) {
|
|
684
774
|
return browser.sessionId;
|
|
685
775
|
}
|
|
@@ -691,8 +781,21 @@ function webdriverPost2(browser, path5, body) {
|
|
|
691
781
|
const id = sessionId(browser);
|
|
692
782
|
return id ? webdriverPost(address(browser), id, path5, body) : Promise.resolve(null);
|
|
693
783
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
784
|
+
function sessionExecutor(browser) {
|
|
785
|
+
const id = sessionId(browser);
|
|
786
|
+
const configured = configuredWebDriver(browser);
|
|
787
|
+
if (!id || !configured.port) {
|
|
788
|
+
return void 0;
|
|
789
|
+
}
|
|
790
|
+
const at = connection(
|
|
791
|
+
configured,
|
|
792
|
+
String(configured.host || "localhost"),
|
|
793
|
+
Number(configured.port)
|
|
794
|
+
);
|
|
795
|
+
return (script, args) => webdriverExecute(at, id, script, args);
|
|
796
|
+
}
|
|
797
|
+
function webdriverExecute2(browser, body, args = []) {
|
|
798
|
+
return webdriverPost2(browser, "execute/sync", { script: body, args });
|
|
696
799
|
}
|
|
697
800
|
|
|
698
801
|
// src/session.ts
|
|
@@ -706,7 +809,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
|
|
|
706
809
|
runner,
|
|
707
810
|
takeScreenshot: () => webdriverGet2(browser, "screenshot"),
|
|
708
811
|
...native ? {} : {
|
|
709
|
-
runScript: (src) =>
|
|
812
|
+
runScript: (src) => webdriverExecute2(browser, `return (${src})`),
|
|
710
813
|
getUrl: () => webdriverGet2(browser, "url"),
|
|
711
814
|
getTitle: () => webdriverGet2(browser, "title")
|
|
712
815
|
}
|
|
@@ -714,6 +817,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
|
|
|
714
817
|
}
|
|
715
818
|
|
|
716
819
|
// src/helpers/perfLogs.ts
|
|
820
|
+
var RESOURCE_TIMING_MATCH_MS = 2e3;
|
|
717
821
|
function applyResponseReceived(p, response) {
|
|
718
822
|
const responseHeaders = {};
|
|
719
823
|
for (const [k, v] of Object.entries(response.headers || {})) {
|
|
@@ -739,7 +843,9 @@ function startPendingRequest(params, entry, pending) {
|
|
|
739
843
|
url: req.url,
|
|
740
844
|
method: req.method,
|
|
741
845
|
requestHeaders: req.headers ?? {},
|
|
742
|
-
|
|
846
|
+
// Epoch, from the event's own wall clock: every consumer dates a row by
|
|
847
|
+
// `timestamp`, and the monotonic `startTime` only diffs within a session.
|
|
848
|
+
timestamp: typeof params.wallTime === "number" ? Math.round(params.wallTime * 1e3) : entry.timestamp,
|
|
743
849
|
startTime
|
|
744
850
|
});
|
|
745
851
|
}
|
|
@@ -797,6 +903,95 @@ function parseNetworkFromPerfLogs(logs) {
|
|
|
797
903
|
}
|
|
798
904
|
return completed;
|
|
799
905
|
}
|
|
906
|
+
function resourceTimingRowsByUrl(rows) {
|
|
907
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
908
|
+
rows.forEach((row, index) => {
|
|
909
|
+
if (row.source !== NETWORK_CAPTURE_SOURCE.RESOURCE_TIMING) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
const indexes = byUrl.get(row.url);
|
|
913
|
+
if (indexes) {
|
|
914
|
+
indexes.push(index);
|
|
915
|
+
} else {
|
|
916
|
+
byUrl.set(row.url, [index]);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
return byUrl;
|
|
920
|
+
}
|
|
921
|
+
function takeNearestResourceTimingRow(indexes, rows, at) {
|
|
922
|
+
let best;
|
|
923
|
+
let bestDelta = RESOURCE_TIMING_MATCH_MS;
|
|
924
|
+
for (const index of indexes) {
|
|
925
|
+
const delta = Math.abs(rows[index].timestamp - at);
|
|
926
|
+
if (delta <= bestDelta) {
|
|
927
|
+
best = index;
|
|
928
|
+
bestDelta = delta;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (best !== void 0) {
|
|
932
|
+
indexes.splice(indexes.indexOf(best), 1);
|
|
933
|
+
}
|
|
934
|
+
return best;
|
|
935
|
+
}
|
|
936
|
+
function overlay(row, entry) {
|
|
937
|
+
const merged = {
|
|
938
|
+
...row,
|
|
939
|
+
method: entry.method,
|
|
940
|
+
requestHeaders: entry.requestHeaders,
|
|
941
|
+
type: entry.type ?? row.type,
|
|
942
|
+
...entry.status !== void 0 ? { status: entry.status } : {},
|
|
943
|
+
...entry.statusText !== void 0 ? { statusText: entry.statusText } : {},
|
|
944
|
+
...entry.responseHeaders ? { responseHeaders: entry.responseHeaders } : {},
|
|
945
|
+
...entry.size !== void 0 ? { size: entry.size } : {},
|
|
946
|
+
...entry.error !== void 0 ? { error: entry.error } : {}
|
|
947
|
+
};
|
|
948
|
+
delete merged.source;
|
|
949
|
+
return merged;
|
|
950
|
+
}
|
|
951
|
+
function withoutLoadsTheLogListed(payload, listed) {
|
|
952
|
+
const requests = payload.networkRequests;
|
|
953
|
+
if (!Array.isArray(requests) || listed.length === 0) {
|
|
954
|
+
return payload;
|
|
955
|
+
}
|
|
956
|
+
const rows = requests;
|
|
957
|
+
const rowsByUrl = resourceTimingRowsByUrl(rows);
|
|
958
|
+
const taken = /* @__PURE__ */ new Set();
|
|
959
|
+
for (const load of listed) {
|
|
960
|
+
const index = takeNearestResourceTimingRow(
|
|
961
|
+
rowsByUrl.get(load.url) ?? [],
|
|
962
|
+
rows,
|
|
963
|
+
load.timestamp
|
|
964
|
+
);
|
|
965
|
+
if (index !== void 0) {
|
|
966
|
+
taken.add(index);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (taken.size === 0) {
|
|
970
|
+
return payload;
|
|
971
|
+
}
|
|
972
|
+
return {
|
|
973
|
+
...payload,
|
|
974
|
+
networkRequests: rows.filter((_, index) => !taken.has(index))
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function upgradeResourceTimingRows(incoming, existing) {
|
|
978
|
+
const rowsByUrl = resourceTimingRowsByUrl(existing);
|
|
979
|
+
const upgrades = [];
|
|
980
|
+
const fresh = [];
|
|
981
|
+
for (const entry of incoming) {
|
|
982
|
+
const index = takeNearestResourceTimingRow(
|
|
983
|
+
rowsByUrl.get(entry.url) ?? [],
|
|
984
|
+
existing,
|
|
985
|
+
entry.timestamp
|
|
986
|
+
);
|
|
987
|
+
if (index === void 0) {
|
|
988
|
+
fresh.push(entry);
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
upgrades.push({ index, request: overlay(existing[index], entry) });
|
|
992
|
+
}
|
|
993
|
+
return { upgrades, fresh };
|
|
994
|
+
}
|
|
800
995
|
function dedupeNetworkRequests(incoming, existing) {
|
|
801
996
|
const failedKey = (entry) => {
|
|
802
997
|
try {
|
|
@@ -838,17 +1033,14 @@ function dedupeNetworkRequests(incoming, existing) {
|
|
|
838
1033
|
|
|
839
1034
|
// src/session.ts
|
|
840
1035
|
var log4 = logger4("@wdio/nightwatch-devtools:SessionCapturer");
|
|
841
|
-
function unwrapDriverValue(result) {
|
|
842
|
-
if (result && typeof result === "object" && "value" in result) {
|
|
843
|
-
return result.value;
|
|
844
|
-
}
|
|
845
|
-
return result;
|
|
846
|
-
}
|
|
847
1036
|
var SessionCapturer = class extends SessionCapturerBase {
|
|
848
1037
|
#browser;
|
|
849
1038
|
// True once BiDi inspectors are attached — the per-command perf-log network
|
|
850
1039
|
// capture path skips when set, so we don't double-emit network requests.
|
|
851
1040
|
bidiActive = false;
|
|
1041
|
+
/** Whether BiDi has delivered at least one network event; a subscription
|
|
1042
|
+
* that delivers nothing is not a source. */
|
|
1043
|
+
bidiNetworkObserved = false;
|
|
852
1044
|
/** Whether the session was created with `goog:loggingPrefs.performance`, set
|
|
853
1045
|
* from the capabilities at session init. A perf log exists only because a
|
|
854
1046
|
* session asked for one, so without it every fetch is a round trip that can
|
|
@@ -857,6 +1049,10 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
857
1049
|
* 21 of them in a two-test run, where XCUITest lists `performance` among its
|
|
858
1050
|
* supported types and then serves none of it. */
|
|
859
1051
|
perfLogsRequested = false;
|
|
1052
|
+
/** Loads the performance log listed before the page's drain could, spent
|
|
1053
|
+
* at the next drain that reaches the driver, whatever it finds, and
|
|
1054
|
+
* emptied with the session that listed them. */
|
|
1055
|
+
loadsListedByLog = [];
|
|
860
1056
|
/** True once the collector is registered to run at document-start. Every
|
|
861
1057
|
* document then instruments and anchors itself, so the paths that exist to
|
|
862
1058
|
* notice a navigation after the fact — re-injection and the settle poll — have
|
|
@@ -869,6 +1065,11 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
869
1065
|
runner = nightwatchRunnerId(false);
|
|
870
1066
|
actionSnapshots = [];
|
|
871
1067
|
snapshotCaptures = [];
|
|
1068
|
+
/** Native app network from appium-interceptor's proxy; started per session. */
|
|
1069
|
+
nativeNetwork = new NativeNetworkCapture(
|
|
1070
|
+
this,
|
|
1071
|
+
(level, message) => log4[level](message)
|
|
1072
|
+
);
|
|
872
1073
|
constructor(devtoolsOptions = {}, browser) {
|
|
873
1074
|
super(devtoolsOptions);
|
|
874
1075
|
this.#browser = browser;
|
|
@@ -895,6 +1096,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
895
1096
|
const commandId = this.commandCounter++;
|
|
896
1097
|
const commandLogEntry = {
|
|
897
1098
|
_id: commandId,
|
|
1099
|
+
id: commandId,
|
|
898
1100
|
command,
|
|
899
1101
|
args,
|
|
900
1102
|
result,
|
|
@@ -904,7 +1106,8 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
904
1106
|
sequence: meta.sequence,
|
|
905
1107
|
callSource: meta.callSource,
|
|
906
1108
|
testUid: meta.testUid,
|
|
907
|
-
selector: meta.selector
|
|
1109
|
+
selector: meta.selector,
|
|
1110
|
+
parentId: meta.parentId
|
|
908
1111
|
};
|
|
909
1112
|
this.commandsLog.push(commandLogEntry);
|
|
910
1113
|
const isNavigationCommand = NAVIGATION_COMMANDS.some(
|
|
@@ -947,15 +1150,18 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
947
1150
|
return;
|
|
948
1151
|
}
|
|
949
1152
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1153
|
+
const payload = await webdriverExecute2(
|
|
1154
|
+
this.#browser,
|
|
1155
|
+
CAPTURE_PERFORMANCE_SCRIPT
|
|
953
1156
|
);
|
|
954
|
-
applyPerformanceData(
|
|
1157
|
+
if (applyPerformanceData(
|
|
955
1158
|
commandLogEntry,
|
|
956
|
-
payload,
|
|
1159
|
+
payload ?? void 0,
|
|
957
1160
|
typeof args[0] === "string" ? args[0] : void 0
|
|
958
|
-
);
|
|
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
|
+
}
|
|
959
1165
|
}
|
|
960
1166
|
/**
|
|
961
1167
|
* Ingest a pre-built assertion entry (native `browser.assert`/`verify`
|
|
@@ -985,7 +1191,8 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
985
1191
|
const idx = this.commandsLog.findIndex(
|
|
986
1192
|
(c) => c._id === oldId
|
|
987
1193
|
);
|
|
988
|
-
const
|
|
1194
|
+
const previous = idx !== -1 ? this.commandsLog[idx] : void 0;
|
|
1195
|
+
const oldTimestamp = previous?.timestamp ?? 0;
|
|
989
1196
|
if (idx !== -1) {
|
|
990
1197
|
this.commandsLog.splice(idx, 1);
|
|
991
1198
|
}
|
|
@@ -994,6 +1201,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
994
1201
|
const commandId = this.commandCounter++;
|
|
995
1202
|
const entry = {
|
|
996
1203
|
_id: commandId,
|
|
1204
|
+
id: previous?.id ?? commandId,
|
|
997
1205
|
command,
|
|
998
1206
|
args,
|
|
999
1207
|
result,
|
|
@@ -1003,18 +1211,68 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1003
1211
|
sequence: meta.sequence,
|
|
1004
1212
|
callSource: meta.callSource,
|
|
1005
1213
|
testUid: meta.testUid,
|
|
1006
|
-
selector: meta.selector
|
|
1214
|
+
selector: meta.selector,
|
|
1215
|
+
parentId: meta.parentId
|
|
1007
1216
|
};
|
|
1008
1217
|
this.commandsLog.push(entry);
|
|
1009
1218
|
return { entry, oldTimestamp };
|
|
1010
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
|
+
}
|
|
1011
1269
|
/** Birth time of the document this capturer last observed, so a replacement
|
|
1012
1270
|
* is still visible to a poll that starts after the navigation committed. */
|
|
1013
1271
|
lastDocumentOrigin;
|
|
1014
1272
|
/** The current document's own birth time, which is what a DOM anchor is
|
|
1015
1273
|
* stamped with. Undefined when the session cannot answer. */
|
|
1016
1274
|
async documentOrigin(browser) {
|
|
1017
|
-
return
|
|
1275
|
+
return webdriverExecute2(browser, "return performance.timeOrigin;").then((origin) => origin ?? void 0).catch(() => void 0);
|
|
1018
1276
|
}
|
|
1019
1277
|
/**
|
|
1020
1278
|
* Wait for a navigation to actually replace the document, then drain with a
|
|
@@ -1063,11 +1321,7 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1063
1321
|
if (!replaced) {
|
|
1064
1322
|
return;
|
|
1065
1323
|
}
|
|
1066
|
-
await this.captureTrace(
|
|
1067
|
-
browser,
|
|
1068
|
-
true,
|
|
1069
|
-
this.commandsLog[this.commandsLog.length - 1]?.timestamp
|
|
1070
|
-
);
|
|
1324
|
+
await this.captureTrace(browser, true, this.#newestEndedTimestamp());
|
|
1071
1325
|
}
|
|
1072
1326
|
/**
|
|
1073
1327
|
* Take a screenshot by calling the WebDriver HTTP endpoint directly, bypassing
|
|
@@ -1097,26 +1351,25 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1097
1351
|
return;
|
|
1098
1352
|
}
|
|
1099
1353
|
try {
|
|
1100
|
-
const alreadyPresent = await
|
|
1354
|
+
const alreadyPresent = await webdriverExecute2(
|
|
1101
1355
|
browser,
|
|
1102
|
-
|
|
1356
|
+
COLLECTOR_READY_SCRIPT
|
|
1103
1357
|
);
|
|
1104
1358
|
if (alreadyPresent === true) {
|
|
1105
1359
|
return;
|
|
1106
1360
|
}
|
|
1107
1361
|
const scriptContent = await loadInjectableScript();
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const hasCollector = await pollUntilReady(
|
|
1116
|
-
async () => await webdriverExecute(
|
|
1362
|
+
const injected = await webdriverExecute2(
|
|
1363
|
+
browser,
|
|
1364
|
+
COLLECTOR_INJECT_SCRIPT,
|
|
1365
|
+
[scriptContent]
|
|
1366
|
+
);
|
|
1367
|
+
const hasCollector = injected === true || await pollUntilReady(
|
|
1368
|
+
async () => await webdriverExecute2(
|
|
1117
1369
|
browser,
|
|
1118
|
-
|
|
1119
|
-
) === true
|
|
1370
|
+
COLLECTOR_READY_SCRIPT
|
|
1371
|
+
) === true,
|
|
1372
|
+
{ checkFirst: true }
|
|
1120
1373
|
);
|
|
1121
1374
|
if (hasCollector) {
|
|
1122
1375
|
log4.info("\u2713 Script injected and collector ready");
|
|
@@ -1128,23 +1381,6 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1128
1381
|
throw err;
|
|
1129
1382
|
}
|
|
1130
1383
|
}
|
|
1131
|
-
/**
|
|
1132
|
-
* Capture Chrome DevTools browser console logs via WebDriver log API.
|
|
1133
|
-
* Requires loggingPrefs: { browser: 'ALL' } in Chrome capabilities.
|
|
1134
|
-
*/
|
|
1135
|
-
async captureBrowserLogs(browser) {
|
|
1136
|
-
try {
|
|
1137
|
-
const rawLogs = await browser.getLog("browser");
|
|
1138
|
-
const logs = unwrapDriverValue(rawLogs);
|
|
1139
|
-
if (!Array.isArray(logs) || logs.length === 0) {
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
const entries = mapChromeBrowserLogs(logs);
|
|
1143
|
-
this.consoleLogs.push(...entries);
|
|
1144
|
-
this.sendUpstream("consoleLogs", entries);
|
|
1145
|
-
} catch {
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
1384
|
/**
|
|
1149
1385
|
* Parse Chrome performance logs to extract network request entries.
|
|
1150
1386
|
* Requires loggingPrefs: { performance: 'ALL' } in Chrome capabilities.
|
|
@@ -1153,33 +1389,39 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1153
1389
|
if (this.bidiActive || !this.perfLogsRequested) {
|
|
1154
1390
|
return;
|
|
1155
1391
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/** A log entry for a load the page already reported replaces that row in
|
|
1404
|
+
* place, under the row's own id; the rest are appended once. */
|
|
1405
|
+
#ingestPerfLogEntries(entries) {
|
|
1406
|
+
const { upgrades, fresh } = upgradeResourceTimingRows(
|
|
1407
|
+
entries,
|
|
1408
|
+
this.networkRequests
|
|
1409
|
+
);
|
|
1410
|
+
for (const { index, request } of upgrades) {
|
|
1411
|
+
this.networkRequests[index] = request;
|
|
1412
|
+
}
|
|
1413
|
+
const deduped = dedupeNetworkRequests(
|
|
1414
|
+
fresh,
|
|
1415
|
+
this.networkRequests
|
|
1416
|
+
);
|
|
1417
|
+
const appended = deduped.map((d) => ({ ...d, type: d.type ?? "other" }));
|
|
1418
|
+
this.networkRequests.push(...appended);
|
|
1419
|
+
this.loadsListedByLog.push(
|
|
1420
|
+
...appended.map(({ url, timestamp }) => ({ url, timestamp }))
|
|
1421
|
+
);
|
|
1422
|
+
const sent = [...upgrades.map((upgrade) => upgrade.request), ...appended];
|
|
1423
|
+
if (sent.length > 0) {
|
|
1424
|
+
this.sendUpstream("networkRequests", sent);
|
|
1183
1425
|
}
|
|
1184
1426
|
}
|
|
1185
1427
|
/** `forceAnchor` forces a full-DOM anchor of the current document before
|
|
@@ -1189,28 +1431,45 @@ var SessionCapturer = class extends SessionCapturerBase {
|
|
|
1189
1431
|
* that command, since it overrides the inference at ingest. */
|
|
1190
1432
|
async captureTrace(browser, forceAnchor = false, anchorTimestamp) {
|
|
1191
1433
|
if (this.isNativeAppSession) {
|
|
1434
|
+
await this.nativeNetwork.forward({ throttle: true });
|
|
1192
1435
|
return;
|
|
1193
1436
|
}
|
|
1194
1437
|
await this.drainCollector(browser, forceAnchor, anchorTimestamp);
|
|
1195
1438
|
await this.captureNetworkFromPerformanceLogs(browser);
|
|
1196
1439
|
}
|
|
1440
|
+
/** At a test or scenario end, before its slice is written: the proxy drain there is never throttled. */
|
|
1441
|
+
async captureTraceAtTestEnd(browser) {
|
|
1442
|
+
if (this.isNativeAppSession) {
|
|
1443
|
+
await this.nativeNetwork.forward();
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
await this.captureTrace(browser, true);
|
|
1447
|
+
}
|
|
1197
1448
|
async drainCollector(browser, forceAnchor, anchorTimestamp) {
|
|
1198
1449
|
try {
|
|
1199
1450
|
const traceData = await drainCollectorWithRecovery({
|
|
1200
|
-
drain: () =>
|
|
1451
|
+
drain: () => webdriverExecute2(
|
|
1201
1452
|
browser,
|
|
1202
1453
|
collectorDrainExpression(forceAnchor)
|
|
1203
1454
|
),
|
|
1204
1455
|
injectIntoCurrentDocument: () => this.injectScript(browser),
|
|
1205
|
-
currentUrl: () =>
|
|
1456
|
+
currentUrl: () => webdriverExecute2(browser, "return location.href;").then(
|
|
1206
1457
|
(u) => u ?? void 0
|
|
1207
1458
|
),
|
|
1208
1459
|
log: (level, message) => log4[level](message)
|
|
1209
1460
|
});
|
|
1461
|
+
const listedByLog = this.loadsListedByLog;
|
|
1462
|
+
this.loadsListedByLog = [];
|
|
1210
1463
|
if (!traceData) {
|
|
1211
1464
|
return;
|
|
1212
1465
|
}
|
|
1213
|
-
this.processTracePayload(
|
|
1466
|
+
this.processTracePayload(
|
|
1467
|
+
withoutLoadsTheLogListed(traceData, listedByLog),
|
|
1468
|
+
{
|
|
1469
|
+
anchorTimestamp,
|
|
1470
|
+
skipResourceTimingRequests: this.bidiNetworkObserved
|
|
1471
|
+
}
|
|
1472
|
+
);
|
|
1214
1473
|
const mutationCount = Array.isArray(
|
|
1215
1474
|
traceData.mutations
|
|
1216
1475
|
) ? traceData.mutations.length : 0;
|
|
@@ -1613,7 +1872,7 @@ var SuiteManager = class {
|
|
|
1613
1872
|
};
|
|
1614
1873
|
|
|
1615
1874
|
// src/helpers/browserProxy.ts
|
|
1616
|
-
import
|
|
1875
|
+
import logger8 from "@wdio/logger";
|
|
1617
1876
|
|
|
1618
1877
|
// src/helpers/callbackError.ts
|
|
1619
1878
|
function callbackError(result, depth = 0) {
|
|
@@ -1624,17 +1883,23 @@ function callbackError(result, depth = 0) {
|
|
|
1624
1883
|
return void 0;
|
|
1625
1884
|
}
|
|
1626
1885
|
const r = result;
|
|
1627
|
-
if ("
|
|
1628
|
-
return depth < 2 ? callbackError(r.value, depth + 1) : void 0;
|
|
1629
|
-
}
|
|
1630
|
-
if ("passed" in r || !r.error) {
|
|
1886
|
+
if ("passed" in r) {
|
|
1631
1887
|
return void 0;
|
|
1632
1888
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1889
|
+
if (r.error instanceof Error) {
|
|
1890
|
+
return r.error;
|
|
1891
|
+
}
|
|
1892
|
+
if (r.error) {
|
|
1893
|
+
const err = new Error(String(r.message ?? r.error));
|
|
1894
|
+
if (typeof r.stack === "string") {
|
|
1895
|
+
err.stack = r.stack;
|
|
1896
|
+
}
|
|
1897
|
+
return err;
|
|
1898
|
+
}
|
|
1899
|
+
if ("value" in r) {
|
|
1900
|
+
return depth < 2 ? callbackError(r.value, depth + 1) : void 0;
|
|
1636
1901
|
}
|
|
1637
|
-
return
|
|
1902
|
+
return void 0;
|
|
1638
1903
|
}
|
|
1639
1904
|
|
|
1640
1905
|
// src/helpers/assertTarget.ts
|
|
@@ -2109,8 +2374,126 @@ var NativeAssertRecorder = class {
|
|
|
2109
2374
|
}
|
|
2110
2375
|
};
|
|
2111
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
|
+
|
|
2112
2495
|
// src/helpers/browserProxy.ts
|
|
2113
|
-
var
|
|
2496
|
+
var log8 = logger8("@wdio/nightwatch-devtools:browserProxy");
|
|
2114
2497
|
var BrowserProxy = class {
|
|
2115
2498
|
constructor(sessionCapturer, testManager, getCurrentTest, captureAssertions = true, observeSession) {
|
|
2116
2499
|
this.sessionCapturer = sessionCapturer;
|
|
@@ -2118,6 +2501,7 @@ var BrowserProxy = class {
|
|
|
2118
2501
|
this.getCurrentTest = getCurrentTest;
|
|
2119
2502
|
this.captureAssertions = captureAssertions;
|
|
2120
2503
|
this.observeSession = observeSession;
|
|
2504
|
+
this.waits = new WaitRows(sessionCapturer);
|
|
2121
2505
|
this.assertRecorder = new NativeAssertRecorder(
|
|
2122
2506
|
sessionCapturer,
|
|
2123
2507
|
() => this.rowTest(),
|
|
@@ -2140,6 +2524,7 @@ var BrowserProxy = class {
|
|
|
2140
2524
|
* rather than appending, showing only the final execution result.
|
|
2141
2525
|
*/
|
|
2142
2526
|
retryTracker = new RetryTracker();
|
|
2527
|
+
waits;
|
|
2143
2528
|
/**
|
|
2144
2529
|
* Monotonic issue-order counter. Stamped when the test ISSUES a row — a
|
|
2145
2530
|
* driver command at invocation, an assert at enqueue — which is source order.
|
|
@@ -2157,6 +2542,8 @@ var BrowserProxy = class {
|
|
|
2157
2542
|
* the assert path, which runs outside any command callback.
|
|
2158
2543
|
*/
|
|
2159
2544
|
browser = null;
|
|
2545
|
+
/** The test core's value registry currently holds reads for; see {@link scopeReadValuesToRowTest}. */
|
|
2546
|
+
readValueTestUid;
|
|
2160
2547
|
/**
|
|
2161
2548
|
* The test a captured row belongs to: the `it` Nightwatch reports as running
|
|
2162
2549
|
* right now, falling back to the lifecycle test. On the BDD `describe/it`
|
|
@@ -2175,6 +2562,19 @@ var BrowserProxy = class {
|
|
|
2175
2562
|
rowTestUid() {
|
|
2176
2563
|
return this.rowTest()?.uid;
|
|
2177
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
|
+
}
|
|
2178
2578
|
/** Per-command-scope dedup state only. Cucumber calls this per STEP, so it
|
|
2179
2579
|
* must not touch the native-assert buffer — that buffer spans the whole
|
|
2180
2580
|
* scenario and is drained once, at the scenario's pre-quit hook. */
|
|
@@ -2188,6 +2588,8 @@ var BrowserProxy = class {
|
|
|
2188
2588
|
resetTestTracking() {
|
|
2189
2589
|
this.resetCommandTracking();
|
|
2190
2590
|
this.assertRecorder.clear();
|
|
2591
|
+
clearReadValues();
|
|
2592
|
+
this.readValueTestUid = void 0;
|
|
2191
2593
|
}
|
|
2192
2594
|
/** Hand off this test's recorded native-assertion calls and clear the
|
|
2193
2595
|
* buffer so the next test starts fresh. */
|
|
@@ -2200,47 +2602,9 @@ var BrowserProxy = class {
|
|
|
2200
2602
|
setCurrentTestFullPath(path5) {
|
|
2201
2603
|
this.currentTestFullPath = path5;
|
|
2202
2604
|
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Wrap browser navigation methods (url / navigate / navigateTo) to inject
|
|
2205
|
-
* the DevTools script after every navigation.
|
|
2206
|
-
*
|
|
2207
|
-
* Uses `browser` from the closure (not `this` inside perform) so it works
|
|
2208
|
-
* for both standard Nightwatch (chainable API) and Cucumber async/await mode
|
|
2209
|
-
* where `this` inside a perform callback is not the browser.
|
|
2210
|
-
*/
|
|
2605
|
+
/** Inject the DevTools script after every navigation; see {@link wrapNavigation}. */
|
|
2211
2606
|
wrapUrlMethod(browser) {
|
|
2212
|
-
|
|
2213
|
-
const b = browser;
|
|
2214
|
-
const wrapNav = (methodName) => {
|
|
2215
|
-
if (typeof b[methodName] !== "function") {
|
|
2216
|
-
return;
|
|
2217
|
-
}
|
|
2218
|
-
const original = b[methodName].bind(browser);
|
|
2219
|
-
b[methodName] = function(...args) {
|
|
2220
|
-
if (typeof args[0] !== "string") {
|
|
2221
|
-
return original(...args);
|
|
2222
|
-
}
|
|
2223
|
-
const result = original(...args);
|
|
2224
|
-
const injectAndCapture = () => {
|
|
2225
|
-
log7.info(`[nav] ${methodName}(${args[0] ?? ""}) \u2014 injecting script`);
|
|
2226
|
-
return sessionCapturer.injectScript(browser).then(() => sessionCapturer.captureTrace(browser, true)).catch(
|
|
2227
|
-
(err) => log7.error(`Failed to inject script: ${err.message}`)
|
|
2228
|
-
);
|
|
2229
|
-
};
|
|
2230
|
-
const chainable = result;
|
|
2231
|
-
if (chainable && typeof chainable.perform === "function") {
|
|
2232
|
-
void Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2233
|
-
});
|
|
2234
|
-
return result;
|
|
2235
|
-
}
|
|
2236
|
-
return Promise.resolve(result).then(injectAndCapture).catch(() => {
|
|
2237
|
-
});
|
|
2238
|
-
};
|
|
2239
|
-
};
|
|
2240
|
-
wrapNav("url");
|
|
2241
|
-
wrapNav("navigate");
|
|
2242
|
-
wrapNav("navigateTo");
|
|
2243
|
-
log7.info("\u2713 Script injection wrapped");
|
|
2607
|
+
wrapNavigation(browser, this.sessionCapturer);
|
|
2244
2608
|
}
|
|
2245
2609
|
wrapBrowserCommands(browser) {
|
|
2246
2610
|
this.browser = browser;
|
|
@@ -2278,36 +2642,81 @@ var BrowserProxy = class {
|
|
|
2278
2642
|
this.assertRecorder.wrapNamespaces(browser);
|
|
2279
2643
|
}
|
|
2280
2644
|
this.proxiedBrowsers.add(browser);
|
|
2281
|
-
|
|
2645
|
+
log8.info(`\u2713 Wrapped ${wrappedMethods.length} browser methods`);
|
|
2282
2646
|
}
|
|
2283
|
-
handleRetryReplacement(browser, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2647
|
+
handleRetryReplacement(browser, oldId, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2284
2648
|
const { entry, oldTimestamp } = this.sessionCapturer.replaceCommand(
|
|
2285
|
-
|
|
2649
|
+
oldId,
|
|
2286
2650
|
methodName,
|
|
2287
2651
|
logArgs,
|
|
2288
2652
|
serializedResult,
|
|
2289
2653
|
commandError,
|
|
2290
2654
|
meta
|
|
2291
2655
|
);
|
|
2292
|
-
this.retryTracker.setLastId(entry._id ?? null);
|
|
2293
2656
|
this.sessionCapturer.sendReplaceCommand(oldTimestamp, entry);
|
|
2294
2657
|
this.attachScreenshot(browser, entry, methodName, " (retry)");
|
|
2658
|
+
return entry._id ?? null;
|
|
2295
2659
|
}
|
|
2296
|
-
captureFreshCommand(browser, methodName, logArgs, serializedResult, meta,
|
|
2297
|
-
this.retryTracker.setLastSig(cmdSig);
|
|
2298
|
-
this.retryTracker.setLastId(null);
|
|
2660
|
+
captureFreshCommand(browser, methodName, logArgs, serializedResult, meta, commandError) {
|
|
2299
2661
|
this.drainOutgoingPage(browser, methodName);
|
|
2300
2662
|
this.sessionCapturer.captureCommand(methodName, logArgs, serializedResult, commandError, meta).catch(
|
|
2301
|
-
(err) =>
|
|
2663
|
+
(err) => log8.error(`Failed to capture ${methodName}: ${err.message}`)
|
|
2302
2664
|
);
|
|
2303
2665
|
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
2304
2666
|
if (lastCommand) {
|
|
2305
|
-
this.retryTracker.setLastId(lastCommand._id ?? null);
|
|
2306
2667
|
this.sessionCapturer.sendCommand(lastCommand);
|
|
2307
|
-
|
|
2668
|
+
log8.info(`[command] ${methodName}`);
|
|
2308
2669
|
this.attachScreenshot(browser, lastCommand, methodName);
|
|
2309
2670
|
}
|
|
2310
2671
|
this.anchorDestination(browser, methodName);
|
|
2672
|
+
if (!this.isDomMutating(methodName)) {
|
|
2673
|
+
void this.sessionCapturer.nativeNetwork.forward({ throttle: true });
|
|
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 });
|
|
2311
2720
|
}
|
|
2312
2721
|
isDomMutating(methodName) {
|
|
2313
2722
|
return NAVIGATION_COMMANDS.includes(methodName) || [
|
|
@@ -2377,47 +2786,44 @@ var BrowserProxy = class {
|
|
|
2377
2786
|
* into one call because `makeCaptureCallback` is already over the line cap. */
|
|
2378
2787
|
serializeAndAttributeResult(methodName, logArgs, callbackResult) {
|
|
2379
2788
|
const result = serializeCommandResult(callbackResult, methodName);
|
|
2789
|
+
this.scopeReadValuesToRowTest();
|
|
2380
2790
|
rememberReadValue(result, commandTargetSelector(methodName, logArgs));
|
|
2381
2791
|
return result;
|
|
2382
2792
|
}
|
|
2383
2793
|
// Result-capturing callback factory — called by Nightwatch's async queue
|
|
2384
2794
|
// when the command completes. This is where we get the *actual* result.
|
|
2385
|
-
makeCaptureCallback(browser,
|
|
2795
|
+
makeCaptureCallback(browser, cmd) {
|
|
2796
|
+
const { methodName, logArgs, cmdSig, userCallback } = cmd;
|
|
2386
2797
|
return (callbackResult) => {
|
|
2387
2798
|
const commandTimestamp = Date.now();
|
|
2388
2799
|
this.popCommandStackIfMatches(methodName, cmdSig);
|
|
2389
2800
|
const commandError = callbackError(callbackResult);
|
|
2390
2801
|
const serializedResult = commandError ? void 0 : this.serializeAndAttributeResult(methodName, logArgs, callbackResult);
|
|
2391
|
-
const effectiveUid = this.rowTestUid() ?? testUid;
|
|
2392
|
-
if (
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
meta,
|
|
2417
|
-
cmdSig,
|
|
2418
|
-
commandError
|
|
2419
|
-
);
|
|
2420
|
-
}
|
|
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
|
+
);
|
|
2421
2827
|
}
|
|
2422
2828
|
if (userCallback) {
|
|
2423
2829
|
return userCallback(callbackResult);
|
|
@@ -2441,7 +2847,7 @@ var BrowserProxy = class {
|
|
|
2441
2847
|
this.currentTestFullPath = callInfo.filePath;
|
|
2442
2848
|
}
|
|
2443
2849
|
const lastArg = args[args.length - 1];
|
|
2444
|
-
const hasUserCallback = typeof lastArg === "function";
|
|
2850
|
+
const hasUserCallback = typeof lastArg === "function" && !(methodName === "waitUntil" && args.length === 1);
|
|
2445
2851
|
const logArgs = hasUserCallback ? args.slice(0, -1) : args;
|
|
2446
2852
|
const cmdSig = JSON.stringify({
|
|
2447
2853
|
command: methodName,
|
|
@@ -2464,49 +2870,59 @@ var BrowserProxy = class {
|
|
|
2464
2870
|
browserAny.currentTest
|
|
2465
2871
|
)
|
|
2466
2872
|
);
|
|
2467
|
-
const
|
|
2468
|
-
const
|
|
2469
|
-
const sequence = this.issueCounter++;
|
|
2470
|
-
const captureCallback = this.makeCaptureCallback(
|
|
2471
|
-
browser,
|
|
2472
|
-
methodName,
|
|
2473
|
-
logArgs,
|
|
2474
|
-
cmdSig,
|
|
2475
|
-
callSource,
|
|
2476
|
-
hasUserSource,
|
|
2477
|
-
invokedAt,
|
|
2478
|
-
sequence,
|
|
2479
|
-
this.rowTestUid(),
|
|
2480
|
-
userCallback
|
|
2481
|
-
);
|
|
2873
|
+
const cmd = this.pendingCommand(methodName, args);
|
|
2874
|
+
const captureCallback = this.makeCaptureCallback(browser, cmd);
|
|
2482
2875
|
const closeInputWindow = beginInputDispatch(methodName);
|
|
2483
2876
|
const gatedCallback = (callbackResult) => {
|
|
2484
2877
|
closeInputWindow();
|
|
2485
2878
|
return captureCallback(callbackResult);
|
|
2486
2879
|
};
|
|
2487
|
-
const modifiedArgs = [...
|
|
2880
|
+
const modifiedArgs = [...this.waits.args(cmd), gatedCallback];
|
|
2488
2881
|
try {
|
|
2489
2882
|
return originalMethod(...modifiedArgs);
|
|
2490
2883
|
} catch (error) {
|
|
2491
2884
|
closeInputWindow();
|
|
2492
|
-
this.popCommandStackIfMatches(methodName, cmdSig);
|
|
2493
|
-
|
|
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
|
+
}
|
|
2494
2891
|
throw error;
|
|
2495
2892
|
}
|
|
2496
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
|
+
}
|
|
2497
2913
|
captureCommandError(methodName, args, error, callSource) {
|
|
2498
2914
|
const currentTest = this.rowTest();
|
|
2499
2915
|
if (!currentTest) {
|
|
2500
2916
|
return;
|
|
2501
2917
|
}
|
|
2502
2918
|
const normalizedError = toError(error);
|
|
2503
|
-
|
|
2919
|
+
log8.error(`[command error] ${methodName}: ${normalizedError.message}`);
|
|
2504
2920
|
this.sessionCapturer.captureCommand(methodName, args, void 0, normalizedError, {
|
|
2505
2921
|
testUid: currentTest.uid,
|
|
2506
2922
|
callSource,
|
|
2507
2923
|
selector: commandRowSelector(methodName, args)
|
|
2508
2924
|
}).catch(
|
|
2509
|
-
(err) =>
|
|
2925
|
+
(err) => log8.error(`Failed to capture ${methodName}: ${err.message}`)
|
|
2510
2926
|
);
|
|
2511
2927
|
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
2512
2928
|
if (lastCommand) {
|
|
@@ -2526,10 +2942,10 @@ var BrowserProxy = class {
|
|
|
2526
2942
|
attachScreenshot(browser, entry, methodName, suffix = "") {
|
|
2527
2943
|
const ts = entry.timestamp ?? 0;
|
|
2528
2944
|
this.sessionCapturer.takeScreenshotViaHttp(browser).then((screenshot) => {
|
|
2529
|
-
if (screenshot) {
|
|
2945
|
+
if (screenshot && this.sessionCapturer.commandsLog.includes(entry)) {
|
|
2530
2946
|
entry.screenshot = screenshot;
|
|
2531
2947
|
this.sessionCapturer.sendReplaceCommand(ts, entry);
|
|
2532
|
-
|
|
2948
|
+
log8.info(`[screenshot] Attached to ${methodName}${suffix}`);
|
|
2533
2949
|
}
|
|
2534
2950
|
}).catch(() => {
|
|
2535
2951
|
});
|
|
@@ -2537,11 +2953,11 @@ var BrowserProxy = class {
|
|
|
2537
2953
|
};
|
|
2538
2954
|
|
|
2539
2955
|
// src/screencast-session.ts
|
|
2540
|
-
import
|
|
2956
|
+
import logger10 from "@wdio/logger";
|
|
2541
2957
|
|
|
2542
2958
|
// src/screencast.ts
|
|
2543
|
-
import
|
|
2544
|
-
var
|
|
2959
|
+
import logger9 from "@wdio/logger";
|
|
2960
|
+
var log9 = logger9("@wdio/nightwatch-devtools:ScreencastRecorder");
|
|
2545
2961
|
var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
2546
2962
|
#sessionCapturer;
|
|
2547
2963
|
constructor(sessionCapturer, options) {
|
|
@@ -2549,15 +2965,15 @@ var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
|
2549
2965
|
this.#sessionCapturer = sessionCapturer;
|
|
2550
2966
|
}
|
|
2551
2967
|
onPollingStarted(intervalMs) {
|
|
2552
|
-
|
|
2968
|
+
log9.info(
|
|
2553
2969
|
`\u2713 Screencast recording started (polling mode, ${intervalMs} ms interval)`
|
|
2554
2970
|
);
|
|
2555
2971
|
}
|
|
2556
2972
|
onPollingStopped(frameCount) {
|
|
2557
|
-
|
|
2973
|
+
log9.info(`\u2713 Screencast stopped \u2014 ${frameCount} frame(s) collected`);
|
|
2558
2974
|
}
|
|
2559
2975
|
onUnavailable(err) {
|
|
2560
|
-
|
|
2976
|
+
log9.warn(
|
|
2561
2977
|
`Screencast unavailable (${errorMessage(err)}). Recording skipped.`
|
|
2562
2978
|
);
|
|
2563
2979
|
}
|
|
@@ -2571,7 +2987,7 @@ var ScreencastRecorder = class extends ScreencastRecorderBase {
|
|
|
2571
2987
|
};
|
|
2572
2988
|
|
|
2573
2989
|
// src/screencast-session.ts
|
|
2574
|
-
var
|
|
2990
|
+
var log10 = logger10("@wdio/nightwatch-devtools:screencast-session");
|
|
2575
2991
|
async function startScreencast(ctx, browser, sessionId2) {
|
|
2576
2992
|
if (!ctx.screencastOptions.enabled || ctx.screencastRecorder || !sessionId2) {
|
|
2577
2993
|
return;
|
|
@@ -2581,7 +2997,7 @@ async function startScreencast(ctx, browser, sessionId2) {
|
|
|
2581
2997
|
ctx.screencastOptions
|
|
2582
2998
|
);
|
|
2583
2999
|
ctx.screencastSessionId = sessionId2;
|
|
2584
|
-
|
|
3000
|
+
log10.info(`\u{1F3AC} Starting screencast for session ${sessionId2}`);
|
|
2585
3001
|
await ctx.screencastRecorder.start(browser);
|
|
2586
3002
|
}
|
|
2587
3003
|
function needsScreencastRotation(ctx, sessionId2) {
|
|
@@ -2595,16 +3011,16 @@ function rotateScreencastForSession(ctx, browser) {
|
|
|
2595
3011
|
if (!needsScreencastRotation(ctx, sessionId2)) {
|
|
2596
3012
|
return;
|
|
2597
3013
|
}
|
|
2598
|
-
|
|
3014
|
+
log10.info(`\u{1F3AC} Session replaced \u2014 rotating screencast to ${sessionId2}`);
|
|
2599
3015
|
ctx.screencastRotation = ctx.finalizeCurrentScreencast().then(() => startScreencast(ctx, browser, sessionId2)).catch(
|
|
2600
|
-
(err) =>
|
|
3016
|
+
(err) => log10.warn(`Screencast rotation failed: ${errorMessage(err)}`)
|
|
2601
3017
|
).finally(() => {
|
|
2602
3018
|
ctx.screencastRotation = void 0;
|
|
2603
3019
|
});
|
|
2604
3020
|
}
|
|
2605
3021
|
|
|
2606
3022
|
// src/session-init.ts
|
|
2607
|
-
var
|
|
3023
|
+
var log11 = logger11("@wdio/nightwatch-devtools:session-init");
|
|
2608
3024
|
function initReporterChain(ctx) {
|
|
2609
3025
|
ctx.testReporter = new TestReporter(
|
|
2610
3026
|
(suitesData) => {
|
|
@@ -2636,9 +3052,9 @@ function initReporterChain(ctx) {
|
|
|
2636
3052
|
}
|
|
2637
3053
|
function readViewport(browser) {
|
|
2638
3054
|
return resolveViewport(browser.capabilities || {}, {
|
|
2639
|
-
runScript: (body) =>
|
|
3055
|
+
runScript: (body) => webdriverExecute2(browser, body),
|
|
2640
3056
|
getWindowSize: () => webdriverGet2(browser, "window/rect"),
|
|
2641
|
-
onWarn: (message) =>
|
|
3057
|
+
onWarn: (message) => log11.warn(message)
|
|
2642
3058
|
});
|
|
2643
3059
|
}
|
|
2644
3060
|
function wantsPerformanceLog(capabilities, desiredCapabilities) {
|
|
@@ -2671,17 +3087,29 @@ async function broadcastSessionMetadata(ctx, browser) {
|
|
|
2671
3087
|
ctx.sessionCapturer.sendUpstream("metadata", metadata);
|
|
2672
3088
|
const browserName = capabilities.browserName || desiredCapabilities.browserName || "unknown";
|
|
2673
3089
|
const browserVersion = capabilities.browserVersion || capabilities.version || "";
|
|
2674
|
-
|
|
3090
|
+
log11.info(
|
|
2675
3091
|
`\u2713 Browser: ${browserName}${browserVersion ? " " + browserVersion : ""} (session: ${sessionId2})`
|
|
2676
3092
|
);
|
|
3093
|
+
if (ctx.mode === "live" && restoreBrowserFocus({
|
|
3094
|
+
browserName: String(browserName),
|
|
3095
|
+
platformName: String(
|
|
3096
|
+
capabilities.platformName || desiredCapabilities.platformName || ""
|
|
3097
|
+
),
|
|
3098
|
+
host: resolveWebDriverAddress(browser).driverHost,
|
|
3099
|
+
onError: (message) => log11.warn(message)
|
|
3100
|
+
})) {
|
|
3101
|
+
log11.info(
|
|
3102
|
+
"\u2713 Safari brought back to the front (it delivers clicks only there)"
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
2677
3105
|
const wantsPerfLog = wantsPerformanceLog(
|
|
2678
3106
|
capabilities,
|
|
2679
3107
|
desiredCapabilities
|
|
2680
3108
|
);
|
|
2681
3109
|
ctx.sessionCapturer.perfLogsRequested = wantsPerfLog;
|
|
2682
3110
|
if (!wantsPerfLog && !ctx.bidiEnabled) {
|
|
2683
|
-
|
|
2684
|
-
"\u26A0 Network tab will
|
|
3111
|
+
log11.warn(
|
|
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"
|
|
2685
3113
|
);
|
|
2686
3114
|
}
|
|
2687
3115
|
}
|
|
@@ -2691,17 +3119,17 @@ async function tryAttachBidi(ctx, browser) {
|
|
|
2691
3119
|
}
|
|
2692
3120
|
const driver = browser.driver;
|
|
2693
3121
|
if (!driver) {
|
|
2694
|
-
|
|
3122
|
+
log11.warn("bidi:true set but browser.driver unavailable \u2014 skipping");
|
|
2695
3123
|
return;
|
|
2696
3124
|
}
|
|
2697
|
-
const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-
|
|
3125
|
+
const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-C67DYUCA.js");
|
|
2698
3126
|
const ok = await attachBidiHandlers(
|
|
2699
3127
|
driver,
|
|
2700
3128
|
buildBidiSinks(ctx.sessionCapturer)
|
|
2701
3129
|
);
|
|
2702
3130
|
if (ok) {
|
|
2703
3131
|
ctx.sessionCapturer.bidiActive = true;
|
|
2704
|
-
|
|
3132
|
+
log11.info("\u2713 BiDi attached \u2014 perf-log network capture disabled");
|
|
2705
3133
|
}
|
|
2706
3134
|
}
|
|
2707
3135
|
async function tryRegisterPreload(ctx, browser) {
|
|
@@ -2711,13 +3139,19 @@ async function tryRegisterPreload(ctx, browser) {
|
|
|
2711
3139
|
}
|
|
2712
3140
|
ctx.sessionCapturer.preloadRegistered = await registerCollectorPreload(
|
|
2713
3141
|
driver,
|
|
2714
|
-
(level, message) =>
|
|
3142
|
+
(level, message) => log11[level](message)
|
|
2715
3143
|
);
|
|
2716
3144
|
}
|
|
2717
3145
|
async function armCaptureForSession(ctx, browser) {
|
|
2718
3146
|
ctx.armedSessionId = browser.sessionId ?? void 0;
|
|
2719
3147
|
await tryRegisterPreload(ctx, browser);
|
|
2720
3148
|
await tryAttachBidi(ctx, browser);
|
|
3149
|
+
await ctx.sessionCapturer.nativeNetwork.start({
|
|
3150
|
+
requested: requestsInterceptedNetwork(browser.desiredCapabilities),
|
|
3151
|
+
capabilities: browser.capabilities,
|
|
3152
|
+
execute: sessionExecutor(browser),
|
|
3153
|
+
unavailableReason: "the session id or the configured WebDriver port is unknown"
|
|
3154
|
+
});
|
|
2721
3155
|
}
|
|
2722
3156
|
function needsCaptureRearm(ctx, sessionId2) {
|
|
2723
3157
|
return Boolean(ctx.sessionCapturer) && sessionId2 !== void 0 && ctx.armedSessionId !== void 0 && ctx.armedSessionId !== sessionId2;
|
|
@@ -2729,16 +3163,19 @@ async function armReplacedSession(ctx, browser) {
|
|
|
2729
3163
|
}
|
|
2730
3164
|
ctx.sessionCapturer.preloadRegistered = false;
|
|
2731
3165
|
ctx.sessionCapturer.bidiActive = false;
|
|
2732
|
-
|
|
3166
|
+
ctx.sessionCapturer.bidiNetworkObserved = false;
|
|
3167
|
+
ctx.sessionCapturer.loadsListedByLog = [];
|
|
3168
|
+
ctx.sessionCapturer.nativeNetwork.stop();
|
|
3169
|
+
log11.info(`Session replaced \u2014 re-arming capture for ${sessionId2}`);
|
|
2733
3170
|
await armCaptureForSession(ctx, browser);
|
|
2734
3171
|
}
|
|
2735
3172
|
function rearmCaptureForSession(ctx, browser) {
|
|
2736
3173
|
void armReplacedSession(ctx, browser).catch(
|
|
2737
|
-
(err) =>
|
|
3174
|
+
(err) => log11.warn(`Capture re-arm failed: ${errorMessage(err)}`)
|
|
2738
3175
|
);
|
|
2739
3176
|
}
|
|
2740
3177
|
async function rebindSessionToBrowser(ctx, browser) {
|
|
2741
|
-
|
|
3178
|
+
log11.info("Browser session changed \u2014 re-targeting the run capturer");
|
|
2742
3179
|
ctx.isScriptInjected = false;
|
|
2743
3180
|
await broadcastSessionMetadata(ctx, browser);
|
|
2744
3181
|
await armReplacedSession(ctx, browser);
|
|
@@ -2768,7 +3205,7 @@ async function ensureSessionInitialized(ctx, browser) {
|
|
|
2768
3205
|
if (ctx.mode !== "trace") {
|
|
2769
3206
|
const connected = await ctx.sessionCapturer.waitForConnection(3e3);
|
|
2770
3207
|
if (!connected) {
|
|
2771
|
-
|
|
3208
|
+
log11.error("\u274C Worker WebSocket failed to connect!");
|
|
2772
3209
|
}
|
|
2773
3210
|
}
|
|
2774
3211
|
initReporterChain(ctx);
|
|
@@ -2793,7 +3230,7 @@ async function finalizeCurrentScreencast(ctx) {
|
|
|
2793
3230
|
}),
|
|
2794
3231
|
captureFormat: ctx.screencastOptions.captureFormat,
|
|
2795
3232
|
sendUpstream: (scope, data) => ctx.sessionCapturer?.sendUpstream(scope, data),
|
|
2796
|
-
onLog: (level, message) =>
|
|
3233
|
+
onLog: (level, message) => log11[level](message)
|
|
2797
3234
|
});
|
|
2798
3235
|
}
|
|
2799
3236
|
ctx.screencastRecorder = void 0;
|
|
@@ -2851,8 +3288,8 @@ var PluginScreencast = class {
|
|
|
2851
3288
|
};
|
|
2852
3289
|
|
|
2853
3290
|
// src/event-hub.ts
|
|
2854
|
-
import
|
|
2855
|
-
var
|
|
3291
|
+
import logger12 from "@wdio/logger";
|
|
3292
|
+
var log12 = logger12("@wdio/nightwatch-devtools:event-hub");
|
|
2856
3293
|
function makeSessionMetadataHandler(bindings) {
|
|
2857
3294
|
return (data) => {
|
|
2858
3295
|
try {
|
|
@@ -2873,23 +3310,23 @@ function makeSessionMetadataHandler(bindings) {
|
|
|
2873
3310
|
options: bindings.buildMetadataOptions()
|
|
2874
3311
|
});
|
|
2875
3312
|
} catch (err) {
|
|
2876
|
-
|
|
3313
|
+
log12.error(`Error in event handler: ${errorMessage(err)}`);
|
|
2877
3314
|
}
|
|
2878
3315
|
};
|
|
2879
3316
|
}
|
|
2880
3317
|
function registerEventHandlers(eventHub, bindings) {
|
|
2881
3318
|
bindings.setCucumberRunner(eventHub.runner === "cucumber");
|
|
2882
3319
|
if (eventHub.runner === "cucumber") {
|
|
2883
|
-
|
|
3320
|
+
log12.info("\u2713 Cucumber runner detected via NightwatchEventHub");
|
|
2884
3321
|
}
|
|
2885
|
-
|
|
3322
|
+
log12.info("\u2713 NightwatchEventHub registered \u2014 enriched metadata enabled");
|
|
2886
3323
|
const handler = makeSessionMetadataHandler(bindings);
|
|
2887
3324
|
eventHub.on("TestSuiteStarted", handler);
|
|
2888
3325
|
eventHub.on("TestRunStarted", handler);
|
|
2889
3326
|
}
|
|
2890
3327
|
|
|
2891
3328
|
// src/cucumber-lifecycle.ts
|
|
2892
|
-
import
|
|
3329
|
+
import logger13 from "@wdio/logger";
|
|
2893
3330
|
|
|
2894
3331
|
// src/helpers/cucumberResult.ts
|
|
2895
3332
|
function cucumberResultToTestState(result) {
|
|
@@ -3084,7 +3521,7 @@ function flushTestSlice(ctx) {
|
|
|
3084
3521
|
}
|
|
3085
3522
|
|
|
3086
3523
|
// src/cucumber-lifecycle.ts
|
|
3087
|
-
var
|
|
3524
|
+
var log13 = logger13("@wdio/nightwatch-devtools:cucumber");
|
|
3088
3525
|
function attachScenarioToFeature(ctx, featureSuite, scenarioSuite) {
|
|
3089
3526
|
const existingIdx = featureSuite.suites.findIndex(
|
|
3090
3527
|
(s) => s.uid === scenarioSuite.uid
|
|
@@ -3168,7 +3605,7 @@ async function initCucumberScenario(ctx, browser, pickle) {
|
|
|
3168
3605
|
ctx.setCurrentTest(null);
|
|
3169
3606
|
ctx.testReporter.updateSuites();
|
|
3170
3607
|
ctx.wrapBrowserOnce(browser);
|
|
3171
|
-
|
|
3608
|
+
log13.info(`\u{1F952} Scenario: ${scenarioName}`);
|
|
3172
3609
|
}
|
|
3173
3610
|
async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
3174
3611
|
try {
|
|
@@ -3190,7 +3627,7 @@ async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
|
3190
3627
|
}
|
|
3191
3628
|
ctx.incrementCount(scenarioState);
|
|
3192
3629
|
const icon = ctx.testIcon(scenarioState);
|
|
3193
|
-
|
|
3630
|
+
log13.info(
|
|
3194
3631
|
` ${icon} ${pickle?.name ?? "Unknown"} (${(duration / 1e3).toFixed(2)}s)`
|
|
3195
3632
|
);
|
|
3196
3633
|
ctx.testReporter.updateSuites();
|
|
@@ -3199,12 +3636,13 @@ async function finalizeCucumberScenario(ctx, _browser, result, pickle) {
|
|
|
3199
3636
|
ctx.setCurrentTest(null);
|
|
3200
3637
|
}
|
|
3201
3638
|
} catch (err) {
|
|
3202
|
-
|
|
3639
|
+
log13.error(`Failed to finalize Cucumber scenario: ${errorMessage(err)}`);
|
|
3203
3640
|
}
|
|
3204
3641
|
}
|
|
3205
3642
|
async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
|
|
3206
3643
|
const scenario = ctx.getCurrentScenarioSuite();
|
|
3207
3644
|
try {
|
|
3645
|
+
ctx.browserProxy?.closeUnrunWaits();
|
|
3208
3646
|
const assertCalls = ctx.browserProxy?.drainNativeAssertCalls() ?? [];
|
|
3209
3647
|
if (assertCalls.length > 0) {
|
|
3210
3648
|
await captureNativeAssertions(
|
|
@@ -3215,14 +3653,14 @@ async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
|
|
|
3215
3653
|
assertCalls
|
|
3216
3654
|
);
|
|
3217
3655
|
}
|
|
3218
|
-
await ctx.sessionCapturer.
|
|
3656
|
+
await ctx.sessionCapturer.captureTraceAtTestEnd(browser);
|
|
3219
3657
|
flushTestSlice(ctx);
|
|
3220
3658
|
await ctx.emitTestArtifacts(
|
|
3221
3659
|
scenario?.uid,
|
|
3222
3660
|
cucumberResultToTestState(result) === TEST_STATE.FAILED
|
|
3223
3661
|
);
|
|
3224
3662
|
} catch (err) {
|
|
3225
|
-
|
|
3663
|
+
log13.error(
|
|
3226
3664
|
`Failed to capture Cucumber scenario before quit: ${errorMessage(err)}`
|
|
3227
3665
|
);
|
|
3228
3666
|
}
|
|
@@ -3268,7 +3706,7 @@ async function cucumberAfter(ctx, browser, result, pickle) {
|
|
|
3268
3706
|
}
|
|
3269
3707
|
|
|
3270
3708
|
// src/test-lifecycle.ts
|
|
3271
|
-
import
|
|
3709
|
+
import logger15 from "@wdio/logger";
|
|
3272
3710
|
|
|
3273
3711
|
// src/helpers/specFileResolver.ts
|
|
3274
3712
|
import fs4 from "fs";
|
|
@@ -3316,8 +3754,8 @@ function resolveSpecFilePath(testFile, modulePath, srcFolders, cachedPath) {
|
|
|
3316
3754
|
}
|
|
3317
3755
|
|
|
3318
3756
|
// src/helpers/closePreviousTest.ts
|
|
3319
|
-
import
|
|
3320
|
-
var
|
|
3757
|
+
import logger14 from "@wdio/logger";
|
|
3758
|
+
var log14 = logger14("@wdio/nightwatch-devtools:closePreviousTest");
|
|
3321
3759
|
async function closePreviousTest(input) {
|
|
3322
3760
|
const {
|
|
3323
3761
|
runningTest,
|
|
@@ -3336,7 +3774,7 @@ async function closePreviousTest(input) {
|
|
|
3336
3774
|
testManager.updateTestState(runningTest, testState);
|
|
3337
3775
|
testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3338
3776
|
incrementCount(testState);
|
|
3339
|
-
|
|
3777
|
+
log14.info(
|
|
3340
3778
|
` ${testIcon(testState)} ${runningTest.title} (${(runningTest._duration / 1e3).toFixed(2)}s)`
|
|
3341
3779
|
);
|
|
3342
3780
|
} else {
|
|
@@ -3350,13 +3788,13 @@ async function closePreviousTest(input) {
|
|
|
3350
3788
|
);
|
|
3351
3789
|
testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3352
3790
|
incrementCount(TEST_STATE.PASSED);
|
|
3353
|
-
|
|
3791
|
+
log14.info(` \u2705 ${runningTest.title} (${(duration / 1e3).toFixed(2)}s)`);
|
|
3354
3792
|
}
|
|
3355
3793
|
await new Promise((resolve2) => setTimeout(resolve2, TIMING.UI_RENDER_DELAY));
|
|
3356
3794
|
}
|
|
3357
3795
|
|
|
3358
3796
|
// src/test-lifecycle.ts
|
|
3359
|
-
var
|
|
3797
|
+
var log15 = logger15("@wdio/nightwatch-devtools:test-lifecycle");
|
|
3360
3798
|
function resolveSuiteMetadata(ctx, currentTest) {
|
|
3361
3799
|
const moduleName = currentTest.module ?? "";
|
|
3362
3800
|
const testFile = moduleName.split("/").pop() || moduleName || DEFAULTS.FILE_NAME;
|
|
@@ -3367,7 +3805,7 @@ function resolveSuiteMetadata(ctx, currentTest) {
|
|
|
3367
3805
|
ctx.browserProxy.getCurrentTestFullPath() || void 0
|
|
3368
3806
|
);
|
|
3369
3807
|
if (!fullPath) {
|
|
3370
|
-
|
|
3808
|
+
log15.warn(
|
|
3371
3809
|
`[beforeEach] Could not resolve file path for "${testFile}" \u2014 source view will be unavailable`
|
|
3372
3810
|
);
|
|
3373
3811
|
}
|
|
@@ -3412,10 +3850,10 @@ async function startNextTest(ctx, currentSuite, currentTestName, processedTests,
|
|
|
3412
3850
|
test.end = null;
|
|
3413
3851
|
ctx.testReporter.onTestStart(test);
|
|
3414
3852
|
ctx.setCurrentTest(test);
|
|
3415
|
-
|
|
3853
|
+
log15.info(` \u25B6 ${currentTestName}`);
|
|
3416
3854
|
await new Promise((resolve2) => setTimeout(resolve2, TIMING.TEST_START_DELAY));
|
|
3417
3855
|
} else {
|
|
3418
|
-
|
|
3856
|
+
log15.warn(
|
|
3419
3857
|
`Test "${currentTestName}" not found in suite "${currentSuite.title}"`
|
|
3420
3858
|
);
|
|
3421
3859
|
ctx.setCurrentTest(null);
|
|
@@ -3460,7 +3898,7 @@ function closeUnreportedRunningTest(ctx, currentSuite, testFile, results, proces
|
|
|
3460
3898
|
ctx.testManager.markTestAsProcessed(testFile, runningTest.title);
|
|
3461
3899
|
ctx.incrementCount(testState);
|
|
3462
3900
|
const icon = ctx.testIcon(testState);
|
|
3463
|
-
|
|
3901
|
+
log15.info(` ${icon} ${runningTest.title} (${(duration / 1e3).toFixed(2)}s)`);
|
|
3464
3902
|
}
|
|
3465
3903
|
async function closeReportedTestcases(ctx, currentSuite, testFile, testcases, processedTests) {
|
|
3466
3904
|
const testcaseNames = Object.keys(testcases);
|
|
@@ -3476,7 +3914,7 @@ async function closeReportedTestcases(ctx, currentSuite, testFile, testcases, pr
|
|
|
3476
3914
|
ctx.testManager.updateTestState(test, testState, /* @__PURE__ */ new Date(), dur);
|
|
3477
3915
|
ctx.incrementCount(testState);
|
|
3478
3916
|
const icon = ctx.testIcon(testState);
|
|
3479
|
-
|
|
3917
|
+
log15.info(` ${icon} ${currentTestName} (${(dur / 1e3).toFixed(2)}s)`);
|
|
3480
3918
|
}
|
|
3481
3919
|
ctx.testManager.markTestAsProcessed(testFile, currentTestName);
|
|
3482
3920
|
}
|
|
@@ -3518,7 +3956,7 @@ async function closeOutTestcases(ctx, browser) {
|
|
|
3518
3956
|
}
|
|
3519
3957
|
|
|
3520
3958
|
// src/index.ts
|
|
3521
|
-
var
|
|
3959
|
+
var log16 = logger16("@wdio/nightwatch-devtools");
|
|
3522
3960
|
var NightwatchDevToolsPlugin = class {
|
|
3523
3961
|
options;
|
|
3524
3962
|
sessionCapturer;
|
|
@@ -3572,7 +4010,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3572
4010
|
screencast: options.screencast
|
|
3573
4011
|
});
|
|
3574
4012
|
if (screencast.warning) {
|
|
3575
|
-
|
|
4013
|
+
log16.warn(screencast.warning);
|
|
3576
4014
|
}
|
|
3577
4015
|
this.options = {
|
|
3578
4016
|
port: options.port ?? 3e3,
|
|
@@ -3593,7 +4031,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3593
4031
|
};
|
|
3594
4032
|
const policyWarning = tracePolicyModeWarning(options.tracePolicy, mode);
|
|
3595
4033
|
if (policyWarning) {
|
|
3596
|
-
|
|
4034
|
+
log16.warn(policyWarning);
|
|
3597
4035
|
}
|
|
3598
4036
|
this.#screencast = new PluginScreencast(
|
|
3599
4037
|
screencast.options,
|
|
@@ -3814,7 +4252,8 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3814
4252
|
// Rows are tagged per-`it` (BrowserProxy.rowTestUid); `#currentTestUid`
|
|
3815
4253
|
// — the lifecycle test, which the BDD interface pins to the module's
|
|
3816
4254
|
// first `it` — is only the fallback, and stays the artifact/slice key.
|
|
3817
|
-
() => this.browserProxy?.rowTestUid() ?? this.#currentTestUid()
|
|
4255
|
+
() => this.browserProxy?.rowTestUid() ?? this.#currentTestUid(),
|
|
4256
|
+
() => this.browserProxy?.scopeReadValuesToRowTest()
|
|
3818
4257
|
);
|
|
3819
4258
|
}
|
|
3820
4259
|
}
|
|
@@ -3926,6 +4365,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3926
4365
|
}
|
|
3927
4366
|
if (browser && this.sessionCapturer) {
|
|
3928
4367
|
try {
|
|
4368
|
+
this.browserProxy.closeUnrunWaits();
|
|
3929
4369
|
await this.#closeOutTestcases(browser);
|
|
3930
4370
|
if (this.options.captureAssertions) {
|
|
3931
4371
|
await captureNativeAssertions(
|
|
@@ -3936,13 +4376,13 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3936
4376
|
this.browserProxy.drainNativeAssertCalls()
|
|
3937
4377
|
);
|
|
3938
4378
|
}
|
|
3939
|
-
await this.sessionCapturer.
|
|
4379
|
+
await this.sessionCapturer.captureTraceAtTestEnd(browser);
|
|
3940
4380
|
flushTestSlice(this.#getInternals());
|
|
3941
4381
|
const results = browser.currentTest?.results;
|
|
3942
4382
|
const failed = !!results && ((results.errors ?? 0) > 0 || (results.failed ?? 0) > 0);
|
|
3943
4383
|
await this.#emitTestArtifacts(this.#currentTestUid(), failed);
|
|
3944
4384
|
} catch (err) {
|
|
3945
|
-
|
|
4385
|
+
log16.error(`Failed to capture trace: ${errorMessage(err)}`);
|
|
3946
4386
|
}
|
|
3947
4387
|
}
|
|
3948
4388
|
}
|
|
@@ -3978,10 +4418,12 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3978
4418
|
outputDir: this.#outputDir,
|
|
3979
4419
|
captureFormat: this.#screencast.options.captureFormat,
|
|
3980
4420
|
onArtifact: (a) => this.#artifacts.push(a),
|
|
3981
|
-
onLog: (level, msg) =>
|
|
4421
|
+
onLog: (level, msg) => log16[level](msg)
|
|
3982
4422
|
});
|
|
3983
4423
|
}
|
|
3984
4424
|
async after(browser) {
|
|
4425
|
+
this.sessionCapturer?.nativeNetwork.stop();
|
|
4426
|
+
this.browserProxy?.closeUnrunWaits();
|
|
3985
4427
|
await this.#screencast.rotation;
|
|
3986
4428
|
await this.#finalizeCurrentScreencast();
|
|
3987
4429
|
try {
|
|
@@ -3995,14 +4437,14 @@ var NightwatchDevToolsPlugin = class {
|
|
|
3995
4437
|
}
|
|
3996
4438
|
if (!this.#devtoolsBrowser) {
|
|
3997
4439
|
this.testReporter?.updateSuites();
|
|
3998
|
-
|
|
4440
|
+
log16.info("\u267B Rerun complete \u2014 flushing WebSocket...");
|
|
3999
4441
|
await this.sessionCapturer?.closeWebSocket();
|
|
4000
4442
|
return;
|
|
4001
4443
|
}
|
|
4002
|
-
|
|
4444
|
+
log16.info("\u{1F4A1} Please close the DevTools browser window to finish...");
|
|
4003
4445
|
await this.#waitForDevtoolsBrowserClose();
|
|
4004
4446
|
} catch (err) {
|
|
4005
|
-
|
|
4447
|
+
log16.error(`Failed to stop backend: ${errorMessage(err)}`);
|
|
4006
4448
|
}
|
|
4007
4449
|
}
|
|
4008
4450
|
async #finalizeAllSuites(browser) {
|
|
@@ -4043,7 +4485,7 @@ var NightwatchDevToolsPlugin = class {
|
|
|
4043
4485
|
screencastFrames: this.options.filmstrip ? this.#screencast.frames : void 0,
|
|
4044
4486
|
configPath: this.#configPath,
|
|
4045
4487
|
testFilePath: this.browserProxy?.getCurrentTestFullPath?.() ?? void 0,
|
|
4046
|
-
log: (level, msg) =>
|
|
4488
|
+
log: (level, msg) => log16[level](msg)
|
|
4047
4489
|
},
|
|
4048
4490
|
sessionId2
|
|
4049
4491
|
);
|