supercov 4.24.8 → 4.25.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/package.json +9 -9
- package/runtime/javascript/launchSupervisor.mjs +10 -0
- package/runtime/javascript/playwright.mjs +426 -179
- package/runtime/javascript/playwrightCacheThread.mjs +6 -0
- package/runtime/javascript/register.mjs +84 -10
- package/runtime/javascript/resolve-loader.mjs +45 -3
- package/runtime/javascript/runnerEvidence.mjs +16 -1
- package/runtime/javascript/runtime.mjs +223 -22
- package/runtime/javascript/snapshots.mjs +108 -0
- package/runtime/javascript/transport.mjs +36 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two events JSON.stringify would write alike: the same keys in the same
|
|
3
|
+
* order, with the same values. Comparing is several times cheaper than
|
|
4
|
+
* writing both, and a test's pages hand over thousands of events each time
|
|
5
|
+
* one of them leaves.
|
|
6
|
+
*/
|
|
7
|
+
function sameEvent(left, right) {
|
|
8
|
+
if (left === right)
|
|
9
|
+
return true;
|
|
10
|
+
if (left === null || right === null || typeof left !== "object" || typeof right !== "object")
|
|
11
|
+
return false;
|
|
12
|
+
const leftKeys = Object.keys(left);
|
|
13
|
+
const rightKeys = Object.keys(right);
|
|
14
|
+
if (leftKeys.length !== rightKeys.length)
|
|
15
|
+
return false;
|
|
16
|
+
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
17
|
+
const key = leftKeys[index];
|
|
18
|
+
if (key !== rightKeys[index])
|
|
19
|
+
return false;
|
|
20
|
+
const leftValue = left[key];
|
|
21
|
+
const rightValue = right[key];
|
|
22
|
+
if (leftValue === rightValue)
|
|
23
|
+
continue;
|
|
24
|
+
if (leftValue === null || rightValue === null || typeof leftValue !== "object" || typeof rightValue !== "object" ||
|
|
25
|
+
JSON.stringify(leftValue) !== JSON.stringify(rightValue))
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One snapshot holding everything in `snapshots`, each observation once: a
|
|
32
|
+
* document that left gave its evidence, and the next one on its origin
|
|
33
|
+
* restored the same evidence and added to it.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeSnapshots(snapshots) {
|
|
36
|
+
const hits = new Set();
|
|
37
|
+
const events = new Map();
|
|
38
|
+
const decisions = new Map();
|
|
39
|
+
const logicals = new Map();
|
|
40
|
+
const vectorsOf = (map, key, initial) => {
|
|
41
|
+
let entry = map.get(key);
|
|
42
|
+
if (!entry) {
|
|
43
|
+
entry = { ...initial, seen: new Map() };
|
|
44
|
+
map.set(key, entry);
|
|
45
|
+
}
|
|
46
|
+
return entry;
|
|
47
|
+
};
|
|
48
|
+
let previousEvents;
|
|
49
|
+
for (const snapshot of snapshots) {
|
|
50
|
+
for (const id of snapshot?.hits ?? [])
|
|
51
|
+
hits.add(id);
|
|
52
|
+
// A document restores the evidence the one before it on its origin
|
|
53
|
+
// left, in order, before adding its own: the events it repeats from
|
|
54
|
+
// the snapshot before are in `events` already, and are compared
|
|
55
|
+
// instead of serialized.
|
|
56
|
+
const list = snapshot?.events ?? [];
|
|
57
|
+
let start = 0;
|
|
58
|
+
if (previousEvents !== undefined) {
|
|
59
|
+
const shared = Math.min(previousEvents.length, list.length);
|
|
60
|
+
while (start < shared && sameEvent(previousEvents[start], list[start]))
|
|
61
|
+
start += 1;
|
|
62
|
+
}
|
|
63
|
+
for (let index = start; index < list.length; index += 1)
|
|
64
|
+
events.set(JSON.stringify(list[index]), list[index]);
|
|
65
|
+
previousEvents = list;
|
|
66
|
+
for (const decision of snapshot?.decisions ?? []) {
|
|
67
|
+
const entry = vectorsOf(decisions, decision.id, { id: decision.id });
|
|
68
|
+
for (const vector of decision.vectors ?? [])
|
|
69
|
+
entry.seen.set(JSON.stringify(vector), vector);
|
|
70
|
+
}
|
|
71
|
+
for (const logical of snapshot?.logicals ?? []) {
|
|
72
|
+
const entry = vectorsOf(logicals, logical.id, { id: logical.id });
|
|
73
|
+
for (const vector of logical.vectors ?? [])
|
|
74
|
+
entry.seen.set(JSON.stringify(vector), vector);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
decisions: [...decisions.values()].map(({ seen, id }) => ({ id, vectors: [...seen.values()] })),
|
|
79
|
+
hits: [...hits],
|
|
80
|
+
events: [...events.values()],
|
|
81
|
+
logicals: [...logicals.values()].map(({ seen, id }) => ({ id, vectors: [...seen.values()] })),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A snapshot as JSON.parse would give it from the text a page's runtime
|
|
86
|
+
* wrote compactly (runtime.mjs, compactSnapshotText): each event in its own
|
|
87
|
+
* key order, its strings from the shared table. Anything else is returned
|
|
88
|
+
* as it is.
|
|
89
|
+
*/
|
|
90
|
+
export function expandSnapshot(value) {
|
|
91
|
+
if (!value || typeof value !== "object" || value.supercovCompactSnapshot !== 1)
|
|
92
|
+
return value;
|
|
93
|
+
const { order, rest, strings, shapes, rows, hits } = value;
|
|
94
|
+
const events = [];
|
|
95
|
+
for (let position = 0; position < rows.length;) {
|
|
96
|
+
const fields = shapes[rows[position++]];
|
|
97
|
+
const event = {};
|
|
98
|
+
for (const field of fields) {
|
|
99
|
+
const raw = rows[position++];
|
|
100
|
+
event[field.slice(1)] = field[0] === "s" ? strings[raw] : raw;
|
|
101
|
+
}
|
|
102
|
+
events.push(event);
|
|
103
|
+
}
|
|
104
|
+
const snapshot = {};
|
|
105
|
+
for (const key of order)
|
|
106
|
+
snapshot[key] = key === "events" ? events : key === "hits" ? hits.map((index) => strings[index]) : rest[key];
|
|
107
|
+
return snapshot;
|
|
108
|
+
}
|
|
@@ -7,6 +7,42 @@ export const COVERAGE_PHASE_COOKIE = "__supercov_phase";
|
|
|
7
7
|
// so the two cannot be one.
|
|
8
8
|
export const COVERAGE_SCOPE_SITE_COOKIE = "__supercov_scope_site";
|
|
9
9
|
export const COVERAGE_PHASE_SITE_COOKIE = "__supercov_phase_site";
|
|
10
|
+
// The phase a new document starts in: the last action's, since a document
|
|
11
|
+
// loads because of one (a navigation, a click that navigates). An assertion
|
|
12
|
+
// that starts while it loads does not change it.
|
|
13
|
+
export const COVERAGE_DOCUMENT_PHASE_COOKIE = "__supercov_document_phase";
|
|
14
|
+
export const COVERAGE_DOCUMENT_PHASE_SITE_COOKIE = "__supercov_document_phase_site";
|
|
15
|
+
/**
|
|
16
|
+
* Runs at the start of every document of a context, as an init script, so it
|
|
17
|
+
* reads nothing from this module. A document starts in the phase its cookie
|
|
18
|
+
* names. Only this attempt's phase is taken: a copy of the script that a
|
|
19
|
+
* context kept from an earlier test leaves a later test's phase alone.
|
|
20
|
+
*/
|
|
21
|
+
export function startDocumentInPhase({ attemptId, phaseCookie, phaseSiteCookie }) {
|
|
22
|
+
try {
|
|
23
|
+
let phase;
|
|
24
|
+
for (const part of document.cookie.split(";")) {
|
|
25
|
+
const at = part.indexOf("=");
|
|
26
|
+
const name = part.slice(0, at).trim();
|
|
27
|
+
if (name !== phaseCookie && name !== phaseSiteCookie)
|
|
28
|
+
continue;
|
|
29
|
+
const value = decodeURIComponent(part.slice(at + 1));
|
|
30
|
+
if (!value.startsWith(`${attemptId}:`))
|
|
31
|
+
continue;
|
|
32
|
+
phase = value;
|
|
33
|
+
if (name === phaseCookie)
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (!phase)
|
|
37
|
+
return;
|
|
38
|
+
const coverageGlobal = globalThis;
|
|
39
|
+
coverageGlobal.__SUPERCOV_PHASE_ID__ = phase;
|
|
40
|
+
coverageGlobal.__SUPERCOV_ACTIVATE_PROBE_CONTEXT__?.(coverageGlobal.__SUPERCOV_MCDC_TEST_ID__ ?? "unscoped", phase);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// A document that cannot read cookies keeps its stored phase.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
10
46
|
export const COVERAGE_CARRIER_ENV = "SUPERCOV_CONTEXT";
|
|
11
47
|
export const DEFAULT_SERVER_EVIDENCE_ROOT = "/tmp/supercov-server-evidence";
|
|
12
48
|
// Buffer as it was when Supercov loaded. Tests replace globals -- axios's set
|