@descryy/mcp 0.5.0 → 0.6.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/dist/browser/driver.d.ts +51 -2
- package/dist/browser/driver.d.ts.map +1 -1
- package/dist/browser/driver.js +16 -2
- package/dist/browser/driver.js.map +1 -1
- package/dist/browser/evidence.d.ts +9 -0
- package/dist/browser/evidence.d.ts.map +1 -1
- package/dist/browser/evidence.js +10 -2
- package/dist/browser/evidence.js.map +1 -1
- package/dist/browser/fake-driver.d.ts +8 -0
- package/dist/browser/fake-driver.d.ts.map +1 -1
- package/dist/browser/fake-driver.js +22 -1
- package/dist/browser/fake-driver.js.map +1 -1
- package/dist/browser/playwright-driver.d.ts +36 -2
- package/dist/browser/playwright-driver.d.ts.map +1 -1
- package/dist/browser/playwright-driver.js +176 -19
- package/dist/browser/playwright-driver.js.map +1 -1
- package/dist/browser/scenario-runner.d.ts.map +1 -1
- package/dist/browser/scenario-runner.js +3 -1
- package/dist/browser/scenario-runner.js.map +1 -1
- package/dist/browser/tool-support.js +1 -1
- package/dist/browser/tool-support.js.map +1 -1
- package/dist/capped.d.ts +41 -0
- package/dist/capped.d.ts.map +1 -0
- package/dist/capped.js +35 -0
- package/dist/capped.js.map +1 -0
- package/dist/render.d.ts +37 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +52 -4
- package/dist/render.js.map +1 -1
- package/dist/runtime-registry.d.ts +19 -1
- package/dist/runtime-registry.d.ts.map +1 -1
- package/dist/runtime-registry.js +88 -4
- package/dist/runtime-registry.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +23 -1
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +22 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +27 -1
- package/dist/session.js.map +1 -1
- package/dist/tools/analyze.d.ts +23 -3
- package/dist/tools/analyze.d.ts.map +1 -1
- package/dist/tools/analyze.js +34 -2
- package/dist/tools/analyze.js.map +1 -1
- package/dist/tools/browser-save-scenario.d.ts.map +1 -1
- package/dist/tools/browser-save-scenario.js +109 -3
- package/dist/tools/browser-save-scenario.js.map +1 -1
- package/dist/tools/browser-start-session.d.ts.map +1 -1
- package/dist/tools/browser-start-session.js +9 -0
- package/dist/tools/browser-start-session.js.map +1 -1
- package/dist/tools/contracts.d.ts +41 -10
- package/dist/tools/contracts.d.ts.map +1 -1
- package/dist/tools/contracts.js +117 -17
- package/dist/tools/contracts.js.map +1 -1
- package/dist/tools/cross-pr.d.ts +39 -10
- package/dist/tools/cross-pr.d.ts.map +1 -1
- package/dist/tools/cross-pr.js +113 -29
- package/dist/tools/cross-pr.js.map +1 -1
- package/dist/tools/git-diff.d.ts.map +1 -1
- package/dist/tools/git-diff.js +30 -2
- package/dist/tools/git-diff.js.map +1 -1
- package/dist/tools/observe-runtime.js +1 -1
- package/dist/tools/observe-runtime.js.map +1 -1
- package/dist/tools/observe-tests.d.ts +12 -0
- package/dist/tools/observe-tests.d.ts.map +1 -1
- package/dist/tools/observe-tests.js +59 -5
- package/dist/tools/observe-tests.js.map +1 -1
- package/dist/tools/pre-push.d.ts +40 -7
- package/dist/tools/pre-push.d.ts.map +1 -1
- package/dist/tools/pre-push.js +65 -23
- package/dist/tools/pre-push.js.map +1 -1
- package/dist/tools/scope.d.ts.map +1 -1
- package/dist/tools/scope.js +20 -4
- package/dist/tools/scope.js.map +1 -1
- package/dist/tools/validate.d.ts +40 -1
- package/dist/tools/validate.d.ts.map +1 -1
- package/dist/tools/validate.js +150 -27
- package/dist/tools/validate.js.map +1 -1
- package/package.json +20 -12
package/dist/browser/driver.d.ts
CHANGED
|
@@ -47,8 +47,21 @@ export interface SnapshotElement {
|
|
|
47
47
|
* read by a human, **not** so a caller can act on it: every verb takes a
|
|
48
48
|
* `ref`, never a selector, because a ref ties the action to a snapshot the
|
|
49
49
|
* agent actually saw and a selector does not.
|
|
50
|
+
*
|
|
51
|
+
* Also what a scenario stores to find the element again in a *later*
|
|
52
|
+
* session (`scenario-resolve.ts`), which is the property that actually
|
|
53
|
+
* needs it to be durable — a value scoped to this one snapshot would defeat
|
|
54
|
+
* that the moment the generation counter moved on.
|
|
55
|
+
*
|
|
56
|
+
* **Null when no candidate resolves to exactly one element on the page.**
|
|
57
|
+
* A driver must never fabricate a selector that might not match, or might
|
|
58
|
+
* match the wrong thing — that is precision over recall (rule 2) applied
|
|
59
|
+
* here: an honest null beats a value that looks like a selector and isn't
|
|
60
|
+
* one. `browser_save_scenario` refuses to save a step naming such an
|
|
61
|
+
* element, by name, rather than writing a scenario doomed to fail at
|
|
62
|
+
* replay.
|
|
50
63
|
*/
|
|
51
|
-
readonly selector: string;
|
|
64
|
+
readonly selector: string | null;
|
|
52
65
|
}
|
|
53
66
|
export interface PageSnapshot {
|
|
54
67
|
readonly url: string;
|
|
@@ -125,11 +138,37 @@ export interface ObservedConsoleError {
|
|
|
125
138
|
readonly scriptUrlMapping: readonly ScriptUrlOutcome[];
|
|
126
139
|
readonly at: string;
|
|
127
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* How a request's lifecycle actually ended, when the collector can tell.
|
|
143
|
+
*
|
|
144
|
+
* `status: null` used to carry two different facts at once: "this will never
|
|
145
|
+
* answer" and "it has not answered *yet*". `"pending"` splits the second one
|
|
146
|
+
* back out — a request still in flight when the observation window closed is
|
|
147
|
+
* neither a success nor a failure, and folding it into either taught a false
|
|
148
|
+
* positive on a perfectly healthy, still-loading page.
|
|
149
|
+
*
|
|
150
|
+
* - `"answered"` — a real status came back, carried in `status`.
|
|
151
|
+
* - `"unanswered"` — a real failure: Playwright's own `requestfailed` fired,
|
|
152
|
+
* or the page closed with this request outstanding.
|
|
153
|
+
* - `"pending"` — still in flight when the drain happened. Not a verdict
|
|
154
|
+
* either way; reported under its own key rather than as a failure.
|
|
155
|
+
*/
|
|
156
|
+
export type RequestOutcome = "answered" | "unanswered" | "pending";
|
|
128
157
|
export interface ObservedRequest {
|
|
129
158
|
readonly method: string;
|
|
130
159
|
readonly url: string;
|
|
131
160
|
/** Null when the request produced no response at all, which is a different fact from a 500. */
|
|
132
161
|
readonly status: number | null;
|
|
162
|
+
/**
|
|
163
|
+
* How this request's lifecycle ended, when the collector reports it.
|
|
164
|
+
*
|
|
165
|
+
* Optional on purpose: the field is `descry-runtime`'s browser collector to
|
|
166
|
+
* populate (a separate lane's work), and this port must not assume that
|
|
167
|
+
* build is running. Absent means no outcome information at all — see
|
|
168
|
+
* `isFailedRequest`, which degrades to its pre-existing status-only rule
|
|
169
|
+
* rather than mis-reading an older build's silence as a new fact.
|
|
170
|
+
*/
|
|
171
|
+
readonly outcome?: RequestOutcome;
|
|
133
172
|
/** The `fetch()` call site as raw text, when the initiator capture claimed one. Null is honest absence. */
|
|
134
173
|
readonly stackText: string | null;
|
|
135
174
|
/** Per-URL mapping outcomes for that call site. See `ScriptUrlOutcome`. */
|
|
@@ -146,7 +185,17 @@ export interface ObservedRequest {
|
|
|
146
185
|
readonly stackTrace: StackTrace | null;
|
|
147
186
|
readonly at: string;
|
|
148
187
|
}
|
|
149
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* A request that did not succeed. Mechanical, not a judgement.
|
|
190
|
+
*
|
|
191
|
+
* With no `outcome` reported (an older `descry-runtime` build), this keeps
|
|
192
|
+
* the original rule exactly: no response at all, or a 4xx/5xx status. With an
|
|
193
|
+
* `outcome`, `"pending"` is deliberately excluded — a request still in flight
|
|
194
|
+
* when the window closed is not evidence of anything — and `"unanswered"` is
|
|
195
|
+
* always a failure regardless of `status`, since Playwright's own
|
|
196
|
+
* `requestfailed` (or a page closing mid-request) is a stronger signal than
|
|
197
|
+
* an absent status by itself.
|
|
198
|
+
*/
|
|
150
199
|
export declare function isFailedRequest(request: ObservedRequest): boolean;
|
|
151
200
|
/** Everything witnessed since the last drain. Raw — see this file's header. */
|
|
152
201
|
export interface BrowserObservation {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB
|
|
1
|
+
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"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"}
|
package/dist/browser/driver.js
CHANGED
|
@@ -36,9 +36,23 @@
|
|
|
36
36
|
* Rule 1 holds here as everywhere — nothing in this file names a language, a
|
|
37
37
|
* framework, or a file extension.
|
|
38
38
|
*/
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* A request that did not succeed. Mechanical, not a judgement.
|
|
41
|
+
*
|
|
42
|
+
* With no `outcome` reported (an older `descry-runtime` build), this keeps
|
|
43
|
+
* the original rule exactly: no response at all, or a 4xx/5xx status. With an
|
|
44
|
+
* `outcome`, `"pending"` is deliberately excluded — a request still in flight
|
|
45
|
+
* when the window closed is not evidence of anything — and `"unanswered"` is
|
|
46
|
+
* always a failure regardless of `status`, since Playwright's own
|
|
47
|
+
* `requestfailed` (or a page closing mid-request) is a stronger signal than
|
|
48
|
+
* an absent status by itself.
|
|
49
|
+
*/
|
|
40
50
|
export function isFailedRequest(request) {
|
|
41
|
-
|
|
51
|
+
if (request.outcome === undefined)
|
|
52
|
+
return request.status === null || request.status >= 400;
|
|
53
|
+
if (request.outcome === "pending")
|
|
54
|
+
return false;
|
|
55
|
+
return request.outcome === "unanswered" || (request.status !== null && request.status >= 400);
|
|
42
56
|
}
|
|
43
57
|
export const EMPTY_OBSERVATION = { actions: [], consoleErrors: [], requests: [], unsettled: [] };
|
|
44
58
|
/** The browser died. Distinct from a closed session: a crash invalidates the id rather than ending it cleanly. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAgKH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,OAAwB;IACtD,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC;IAC3F,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAChD,OAAO,OAAO,CAAC,OAAO,KAAK,YAAY,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;AAChG,CAAC;AAsCD,MAAM,CAAC,MAAM,iBAAiB,GAAuB,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AA0BrH,kHAAkH;AAClH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IAC1C,SAAS,CAAS;IAE3B,YAAY,SAAiB;QAC3B,KAAK,CACH,mBAAmB,SAAS,yEAAyE;YACnG,gGAAgG;YAChG,+EAA+E,CAClF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,GAAW;QACrB,KAAK,CACH,IAAI,GAAG,+FAA+F;YACpG,kFAAkF,CACrF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -29,6 +29,15 @@ export interface BrowserEvidenceReport {
|
|
|
29
29
|
* frontend action reached a backend route.
|
|
30
30
|
*/
|
|
31
31
|
readonly failedRequests: readonly ObservedRequest[];
|
|
32
|
+
/**
|
|
33
|
+
* Requests still in flight when the observation window closed.
|
|
34
|
+
*
|
|
35
|
+
* Not a failure and not a success — `isFailedRequest` deliberately excludes
|
|
36
|
+
* these. Reported under its own key rather than silently absent, because a
|
|
37
|
+
* healthy request that simply had not answered yet must not read the same
|
|
38
|
+
* as a page that issued nothing.
|
|
39
|
+
*/
|
|
40
|
+
readonly pendingRequests: readonly ObservedRequest[];
|
|
32
41
|
/** How many requests the page issued in total, so the list above is read as a subset and not as all of it. */
|
|
33
42
|
readonly totalRequests: number;
|
|
34
43
|
readonly actions: BrowserObservation["actions"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/browser/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IAC5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,eAAe,EAAE,CAAC;IACpD,8GAA8G;IAC9G,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,
|
|
1
|
+
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/browser/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IAC5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,eAAe,EAAE,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,SAAS,eAAe,EAAE,CAAC;IACrD,8GAA8G;IAC9G,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CASrF;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAmCrE"}
|
package/dist/browser/evidence.js
CHANGED
|
@@ -20,13 +20,14 @@ export function evidenceReport(observation) {
|
|
|
20
20
|
return {
|
|
21
21
|
consoleErrors: observation.consoleErrors,
|
|
22
22
|
failedRequests: observation.requests.filter(isFailedRequest),
|
|
23
|
+
pendingRequests: observation.requests.filter((request) => request.outcome === "pending"),
|
|
23
24
|
totalRequests: observation.requests.length,
|
|
24
25
|
actions: observation.actions,
|
|
25
26
|
unsettled: observation.unsettled,
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
export function isEmpty(report) {
|
|
29
|
-
return report.consoleErrors.length === 0 && report.failedRequests.length === 0;
|
|
30
|
+
return report.consoleErrors.length === 0 && report.failedRequests.length === 0 && report.pendingRequests.length === 0;
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
33
|
* The notes any reply carrying evidence must include.
|
|
@@ -45,10 +46,17 @@ export function evidenceNotes(report) {
|
|
|
45
46
|
`activity when Descry stopped waiting, after ${item.waitedMs}ms. Evidence for anything that finished ` +
|
|
46
47
|
"after that point is not in this reply — this is an incomplete observation of that action, not a " +
|
|
47
48
|
"clean one.");
|
|
49
|
+
// Checked before the emptiness gate too, and for the same reason as
|
|
50
|
+
// `unsettledNotes`: a request that is merely still in flight is not a
|
|
51
|
+
// failure and must not be silently folded into a report that then reads as
|
|
52
|
+
// clean when it is really "the window closed before this one finished."
|
|
53
|
+
const pendingNotes = report.pendingRequests.map((request) => `${request.method} ${request.url} was still in flight when the observation window closed. That is not ` +
|
|
54
|
+
"evidence it will fail — the request simply had not answered yet.");
|
|
48
55
|
if (isEmpty(report))
|
|
49
|
-
return unsettledNotes;
|
|
56
|
+
return [...unsettledNotes, ...pendingNotes];
|
|
50
57
|
const notes = [
|
|
51
58
|
...unsettledNotes,
|
|
59
|
+
...pendingNotes,
|
|
52
60
|
`${report.consoleErrors.length} console error(s) and ${report.failedRequests.length} failed request(s) ` +
|
|
53
61
|
"were observed in the browser during this call. They are reported raw and have not been triaged — " +
|
|
54
62
|
"Descry is not claiming any of them is a bug, only that the page produced them. Deciding which " +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/browser/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,eAAe,EAAiD,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/browser/evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,6 +41,8 @@ export interface FakeBrowserDriver extends BrowserDriver {
|
|
|
41
41
|
readonly status: number | null;
|
|
42
42
|
readonly stackText: string | null;
|
|
43
43
|
readonly stackTrace?: ObservedRequest["stackTrace"];
|
|
44
|
+
/** Omit to leave this request's outcome unreported, the way an older `descry-runtime` build would. */
|
|
45
|
+
readonly outcome?: ObservedRequest["outcome"];
|
|
44
46
|
}): void;
|
|
45
47
|
/** Sugar for `emitRequest` with a failing status. */
|
|
46
48
|
emitFailedRequest(sessionId: string, request: {
|
|
@@ -49,6 +51,12 @@ export interface FakeBrowserDriver extends BrowserDriver {
|
|
|
49
51
|
readonly status: number | null;
|
|
50
52
|
readonly stackText: string | null;
|
|
51
53
|
}): void;
|
|
54
|
+
/** Sugar for `emitRequest` still in flight when the window closed — `outcome: "pending"`, `status: null`. */
|
|
55
|
+
emitPendingRequest(sessionId: string, request: {
|
|
56
|
+
readonly method: string;
|
|
57
|
+
readonly url: string;
|
|
58
|
+
readonly stackText: string | null;
|
|
59
|
+
}): void;
|
|
52
60
|
/** Kill the browser behind a session, the way a real crash does — usable goes false, the id stays invalid. */
|
|
53
61
|
crash(sessionId: string): void;
|
|
54
62
|
/** Every session this driver has opened and not closed. What the registry's orphan-cleanup tests assert against. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-driver.d.ts","sourceRoot":"","sources":["../../src/browser/fake-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAGL,KAAK,aAAa,EAOlB,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAC;AAErB,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3G;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,0JAA0J;IAC1J,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,kFAAkF;IAClF,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/G,iGAAiG;IACjG,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QACP,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"fake-driver.d.ts","sourceRoot":"","sources":["../../src/browser/fake-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAGL,KAAK,aAAa,EAOlB,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAC;AAErB,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3G;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,0JAA0J;IAC1J,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,kFAAkF;IAClF,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/G,iGAAiG;IACjG,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QACP,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;QACpD,sGAAsG;QACtG,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;KAC/C,GACA,IAAI,CAAC;IACR,qDAAqD;IACrD,iBAAiB,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAC5H,IAAI,CAAC;IACR,6GAA6G;IAC7G,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3I,8GAA8G;IAC9G,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oHAAoH;IACpH,cAAc,IAAI,SAAS,MAAM,EAAE,CAAC;CACrC;AAeD,wBAAgB,uBAAuB,IAAI,iBAAiB,CA6K3D"}
|
|
@@ -147,11 +147,32 @@ export function createFakeBrowserDriver() {
|
|
|
147
147
|
stateOf(sessionId).buffer.consoleErrors.push({ scriptUrlMapping: [], ...error, at: new Date().toISOString() });
|
|
148
148
|
},
|
|
149
149
|
emitRequest(sessionId, request) {
|
|
150
|
-
|
|
150
|
+
// `outcome` is destructured out and re-added only when present:
|
|
151
|
+
// `exactOptionalPropertyTypes` treats a spread of an optional field as
|
|
152
|
+
// `T | undefined`, which `ObservedRequest`'s own `outcome?:` does not
|
|
153
|
+
// accept — the key must be omitted, never present as `undefined`.
|
|
154
|
+
const { outcome, ...rest } = request;
|
|
155
|
+
stateOf(sessionId).buffer.requests.push({
|
|
156
|
+
stackTrace: null,
|
|
157
|
+
scriptUrlMapping: [],
|
|
158
|
+
...rest,
|
|
159
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
160
|
+
at: new Date().toISOString(),
|
|
161
|
+
});
|
|
151
162
|
},
|
|
152
163
|
emitFailedRequest(sessionId, request) {
|
|
153
164
|
stateOf(sessionId).buffer.requests.push({ scriptUrlMapping: [], ...request, stackTrace: null, at: new Date().toISOString() });
|
|
154
165
|
},
|
|
166
|
+
emitPendingRequest(sessionId, request) {
|
|
167
|
+
stateOf(sessionId).buffer.requests.push({
|
|
168
|
+
scriptUrlMapping: [],
|
|
169
|
+
stackTrace: null,
|
|
170
|
+
...request,
|
|
171
|
+
status: null,
|
|
172
|
+
outcome: "pending",
|
|
173
|
+
at: new Date().toISOString(),
|
|
174
|
+
});
|
|
175
|
+
},
|
|
155
176
|
crash(sessionId) {
|
|
156
177
|
stateOf(sessionId).crashed = true;
|
|
157
178
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-driver.js","sourceRoot":"","sources":["../../src/browser/fake-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,GAWvB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"fake-driver.js","sourceRoot":"","sources":["../../src/browser/fake-driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
* the same reason. Without one, requests are still observed and no caller
|
|
42
42
|
* edge is written; the reply says so.
|
|
43
43
|
*/
|
|
44
|
-
import type { ServiceRootLookup } from "@descryy/runtime-contracts";
|
|
44
|
+
import type { StackTrace, ServiceRootLookup } from "@descryy/runtime-contracts";
|
|
45
45
|
import type { StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
46
|
-
import type { BrowserDriver } from "./driver.ts";
|
|
46
|
+
import type { BrowserDriver, BrowserObservation, ObservedAction, UnsettledAction } from "./driver.ts";
|
|
47
47
|
export interface PlaywrightDriverOptions {
|
|
48
48
|
/**
|
|
49
49
|
* Turns a raw browser stack into resolved frames. Supplied by the caller;
|
|
@@ -80,6 +80,40 @@ type SourceRootResolver = (origin: string) => ServiceRootLookup;
|
|
|
80
80
|
export declare class BrowserBackendMissingError extends Error {
|
|
81
81
|
constructor(cause: unknown);
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* An `Evidence`-shaped record, as the collectors emit it.
|
|
85
|
+
*
|
|
86
|
+
* Typed structurally rather than imported wholesale because only four fields
|
|
87
|
+
* are read: everything else the collectors attach travels to nobody here.
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* Exported for `browser-request-outcome-mapping.test.ts`, which drives
|
|
91
|
+
* `collect()` directly with synthetic evidence records — the real
|
|
92
|
+
* evidence-to-`ObservedRequest` mapping, not the fake driver's fixtures. That
|
|
93
|
+
* test is the regression test for B3: the fake driver lets a fixture declare
|
|
94
|
+
* `outcome` directly, which let this mapping function ship never populating
|
|
95
|
+
* the field at all while every test still passed.
|
|
96
|
+
*/
|
|
97
|
+
export interface EmittedEvidence {
|
|
98
|
+
readonly eventType: string;
|
|
99
|
+
readonly payload: Record<string, unknown>;
|
|
100
|
+
readonly stackTrace: StackTrace | null;
|
|
101
|
+
readonly requestId: string | null;
|
|
102
|
+
readonly timestamp: string;
|
|
103
|
+
}
|
|
83
104
|
export declare function createPlaywrightBrowserDriver(options?: PlaywrightDriverOptions): Promise<BrowserDriver>;
|
|
105
|
+
/**
|
|
106
|
+
* Turn what the collectors emitted into the port's vocabulary.
|
|
107
|
+
*
|
|
108
|
+
* The request/response join is on `requestId`, because a `NETWORK_REQUEST`
|
|
109
|
+
* carries the method, the URL and the call-site stack while only the matching
|
|
110
|
+
* `NETWORK_RESPONSE` knows the status. A request with no response is kept with
|
|
111
|
+
* `status: null` — "never answered" is a different fact from a 500, and the
|
|
112
|
+
* port says so.
|
|
113
|
+
*
|
|
114
|
+
* Exported for `browser-request-outcome-mapping.test.ts` — see
|
|
115
|
+
* `EmittedEvidence`'s doc comment for why.
|
|
116
|
+
*/
|
|
117
|
+
export declare function collect(emitted: readonly EmittedEvidence[], actions: readonly ObservedAction[], unsettled: readonly UnsettledAction[]): BrowserObservation;
|
|
84
118
|
export {};
|
|
85
119
|
//# sourceMappingURL=playwright-driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-driver.d.ts","sourceRoot":"","sources":["../../src/browser/playwright-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"playwright-driver.d.ts","sourceRoot":"","sources":["../../src/browser/playwright-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AA+B7E,OAAO,KAAK,EACV,aAAa,EAGb,kBAAkB,EAElB,cAAc,EAOd,eAAe,EAChB,MAAM,aAAa,CAAC;AAgDrB,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC,uGAAuG;IACvG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;IAChD,mDAAmD;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED,KAAK,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,iBAAiB,CAAC;AAsDhE,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,KAAK,EAAE,OAAO;CAO3B;AAED;;;;;GAKG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,6BAA6B,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,aAAa,CAAC,CAmBjH;AAgXD;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,SAAS,EAAE,SAAS,eAAe,EAAE,GACpC,kBAAkB,CA6FpB"}
|
|
@@ -276,7 +276,115 @@ async function openSession(runtime, session, launchOptions, options) {
|
|
|
276
276
|
el.getAttribute("placeholder") ??
|
|
277
277
|
el.getAttribute("name") ??
|
|
278
278
|
"";
|
|
279
|
-
|
|
279
|
+
// --- a real, re-resolvable selector, computed in this same
|
|
280
|
+
// round trip so role/name/selector are all read off one live
|
|
281
|
+
// DOM state. Everything below runs *inside the page* — it is
|
|
282
|
+
// serialized by Playwright, so it must be entirely self
|
|
283
|
+
// contained (no closures over the outer module).
|
|
284
|
+
// An id that looks generated rather than authored: a hex run
|
|
285
|
+
// (a hashed/uuid-derived id), React's `:r0:`-style useId
|
|
286
|
+
// output, or blank. Rejected because "stable across a reload"
|
|
287
|
+
// is exactly the property a generated id does not have.
|
|
288
|
+
const GENERATED_ID = /[0-9a-f]{8}|:r[0-9a-z]+:|^\s*$/i;
|
|
289
|
+
const FORM_CONTROLS = ["input", "select", "textarea", "button"];
|
|
290
|
+
function escapeAttrValue(value) {
|
|
291
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
292
|
+
}
|
|
293
|
+
function isUnique(candidate) {
|
|
294
|
+
try {
|
|
295
|
+
return el.ownerDocument.querySelectorAll(candidate).length === 1;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// An attribute value that produces an invalid selector (rare,
|
|
299
|
+
// but not impossible) is treated as "no candidate" rather
|
|
300
|
+
// than thrown — the caller falls through to the next tier.
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** The element's own stable-hook selector, or null. Used both on the target itself and while climbing for a structural path's anchor. */
|
|
305
|
+
function stableHookSelector(node) {
|
|
306
|
+
const testId = node.getAttribute("data-testid");
|
|
307
|
+
if (testId !== null && testId.trim() !== "")
|
|
308
|
+
return `[data-testid="${escapeAttrValue(testId)}"]`;
|
|
309
|
+
const testAttr = node.getAttribute("data-test");
|
|
310
|
+
if (testAttr !== null && testAttr.trim() !== "")
|
|
311
|
+
return `[data-test="${escapeAttrValue(testAttr)}"]`;
|
|
312
|
+
const id = node.getAttribute("id");
|
|
313
|
+
if (id !== null && !GENERATED_ID.test(id))
|
|
314
|
+
return `[id="${escapeAttrValue(id)}"]`;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function nthOfTypeSegment(node, parent) {
|
|
318
|
+
const tag = node.tagName.toLowerCase();
|
|
319
|
+
const siblings = parent.querySelectorAll(`:scope > ${tag}`);
|
|
320
|
+
if (siblings.length <= 1)
|
|
321
|
+
return tag;
|
|
322
|
+
for (let i = 0; i < siblings.length; i += 1) {
|
|
323
|
+
if (siblings.item(i) === node)
|
|
324
|
+
return `${tag}:nth-of-type(${i + 1})`;
|
|
325
|
+
}
|
|
326
|
+
return tag;
|
|
327
|
+
}
|
|
328
|
+
/** A bounded path from the nearest ancestor carrying a stable hook, capped at 6 segments. Null when none is found in range. */
|
|
329
|
+
function structuralPath(target) {
|
|
330
|
+
const segments = [];
|
|
331
|
+
let node = target;
|
|
332
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
333
|
+
const parent = node.parentElement;
|
|
334
|
+
if (parent === null)
|
|
335
|
+
return null;
|
|
336
|
+
segments.unshift(nthOfTypeSegment(node, parent));
|
|
337
|
+
const hook = stableHookSelector(parent);
|
|
338
|
+
if (hook !== null) {
|
|
339
|
+
const candidate = `${hook} > ${segments.join(" > ")}`;
|
|
340
|
+
return isUnique(candidate) ? candidate : null;
|
|
341
|
+
}
|
|
342
|
+
node = parent;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function computeSelector(target) {
|
|
347
|
+
// Priority order: first candidate that is unique in the whole
|
|
348
|
+
// document wins. Each tier is checked for uniqueness on its
|
|
349
|
+
// own — a data-testid shared by two elements is not
|
|
350
|
+
// addressable either, and the next tier gets its turn rather
|
|
351
|
+
// than giving up at the first attribute that happens to be
|
|
352
|
+
// present.
|
|
353
|
+
const testId = target.getAttribute("data-testid");
|
|
354
|
+
if (testId !== null && testId.trim() !== "") {
|
|
355
|
+
const candidate = `[data-testid="${escapeAttrValue(testId)}"]`;
|
|
356
|
+
if (isUnique(candidate))
|
|
357
|
+
return candidate;
|
|
358
|
+
}
|
|
359
|
+
const testAttr = target.getAttribute("data-test");
|
|
360
|
+
if (testAttr !== null && testAttr.trim() !== "") {
|
|
361
|
+
const candidate = `[data-test="${escapeAttrValue(testAttr)}"]`;
|
|
362
|
+
if (isUnique(candidate))
|
|
363
|
+
return candidate;
|
|
364
|
+
}
|
|
365
|
+
const id = target.getAttribute("id");
|
|
366
|
+
if (id !== null && !GENERATED_ID.test(id)) {
|
|
367
|
+
const candidate = `[id="${escapeAttrValue(id)}"]`;
|
|
368
|
+
if (isUnique(candidate))
|
|
369
|
+
return candidate;
|
|
370
|
+
}
|
|
371
|
+
const controlTag = target.tagName.toLowerCase();
|
|
372
|
+
const name = target.getAttribute("name");
|
|
373
|
+
if (name !== null && name.trim() !== "" && FORM_CONTROLS.includes(controlTag)) {
|
|
374
|
+
const candidate = `${controlTag}[name="${escapeAttrValue(name)}"]`;
|
|
375
|
+
if (isUnique(candidate))
|
|
376
|
+
return candidate;
|
|
377
|
+
}
|
|
378
|
+
const explicitRole = target.getAttribute("role");
|
|
379
|
+
const ariaLabel = target.getAttribute("aria-label");
|
|
380
|
+
if (explicitRole !== null && ariaLabel !== null && ariaLabel.trim() !== "") {
|
|
381
|
+
const candidate = `[role="${escapeAttrValue(explicitRole)}"][aria-label="${escapeAttrValue(ariaLabel)}"]`;
|
|
382
|
+
if (isUnique(candidate))
|
|
383
|
+
return candidate;
|
|
384
|
+
}
|
|
385
|
+
return structuralPath(target);
|
|
386
|
+
}
|
|
387
|
+
return { role, name: name.slice(0, 200), selector: computeSelector(el) };
|
|
280
388
|
})
|
|
281
389
|
.catch(() => null);
|
|
282
390
|
if (described === null) {
|
|
@@ -288,10 +396,7 @@ async function openSession(runtime, session, launchOptions, options) {
|
|
|
288
396
|
ref,
|
|
289
397
|
role: described.role,
|
|
290
398
|
name: described.name,
|
|
291
|
-
|
|
292
|
-
// selector string. Reporting a fabricated CSS path here would be a
|
|
293
|
-
// selector nothing ever uses and that might not even match.
|
|
294
|
-
selector: `(held handle ${ref})`,
|
|
399
|
+
selector: described.selector,
|
|
295
400
|
};
|
|
296
401
|
refs.set(ref, { element, handle });
|
|
297
402
|
elements.push(element);
|
|
@@ -361,8 +466,11 @@ async function openSession(runtime, session, launchOptions, options) {
|
|
|
361
466
|
* `NETWORK_RESPONSE` knows the status. A request with no response is kept with
|
|
362
467
|
* `status: null` — "never answered" is a different fact from a 500, and the
|
|
363
468
|
* port says so.
|
|
469
|
+
*
|
|
470
|
+
* Exported for `browser-request-outcome-mapping.test.ts` — see
|
|
471
|
+
* `EmittedEvidence`'s doc comment for why.
|
|
364
472
|
*/
|
|
365
|
-
function collect(emitted, actions, unsettled) {
|
|
473
|
+
export function collect(emitted, actions, unsettled) {
|
|
366
474
|
// Two joins, in priority order, because `requestId` is a *correlation
|
|
367
475
|
// header* and not a transport id: it exists only when the application
|
|
368
476
|
// itself propagates one. The fixture's own delete sets `x-request-id`, so
|
|
@@ -370,20 +478,44 @@ function collect(emitted, actions, unsettled) {
|
|
|
370
478
|
// for every request the page issued without one — which is most of them.
|
|
371
479
|
const statusByRequestId = new Map();
|
|
372
480
|
const statusByUrl = new Map();
|
|
481
|
+
// B3: the same two joins, for `outcome`. `descry-runtime` stamps
|
|
482
|
+
// `outcome: "answered"` on every `NETWORK_RESPONSE` and `"unanswered"` on a
|
|
483
|
+
// paired `HTTP_ERROR` (a real `requestfailed`, a collector timeout, or a
|
|
484
|
+
// WebSocket `socketerror`); it never emits `"pending"` itself — a request
|
|
485
|
+
// still open when the drain runs simply has no terminal event at all,
|
|
486
|
+
// which is why `pairedRequestIds`/`pairedUrls` below track "a terminal
|
|
487
|
+
// event existed" separately from "and it named a recognisable outcome".
|
|
488
|
+
const outcomeByRequestId = new Map();
|
|
489
|
+
const outcomeByUrl = new Map();
|
|
490
|
+
const pairedRequestIds = new Set();
|
|
491
|
+
const pairedUrls = new Set();
|
|
373
492
|
for (const evidence of emitted) {
|
|
374
|
-
if (evidence.eventType !== "NETWORK_RESPONSE")
|
|
375
|
-
continue;
|
|
376
|
-
const status = evidence.payload["status"];
|
|
377
|
-
if (typeof status !== "number")
|
|
493
|
+
if (evidence.eventType !== "NETWORK_RESPONSE" && evidence.eventType !== "HTTP_ERROR")
|
|
378
494
|
continue;
|
|
379
|
-
if (evidence.requestId !== null)
|
|
380
|
-
statusByRequestId.set(evidence.requestId, status);
|
|
381
495
|
const url = evidence.payload["url"];
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// asking about.
|
|
496
|
+
if (evidence.requestId !== null)
|
|
497
|
+
pairedRequestIds.add(evidence.requestId);
|
|
385
498
|
if (typeof url === "string")
|
|
386
|
-
|
|
499
|
+
pairedUrls.add(url);
|
|
500
|
+
if (evidence.eventType === "NETWORK_RESPONSE") {
|
|
501
|
+
const status = evidence.payload["status"];
|
|
502
|
+
if (typeof status === "number") {
|
|
503
|
+
if (evidence.requestId !== null)
|
|
504
|
+
statusByRequestId.set(evidence.requestId, status);
|
|
505
|
+
// Last response wins: a page that requests the same URL twice reports
|
|
506
|
+
// the most recent outcome, which is the one a caller reading this
|
|
507
|
+
// reply is asking about.
|
|
508
|
+
if (typeof url === "string")
|
|
509
|
+
statusByUrl.set(url, status);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const outcome = requestOutcomeOf(evidence.payload);
|
|
513
|
+
if (outcome !== undefined) {
|
|
514
|
+
if (evidence.requestId !== null)
|
|
515
|
+
outcomeByRequestId.set(evidence.requestId, outcome);
|
|
516
|
+
if (typeof url === "string")
|
|
517
|
+
outcomeByUrl.set(url, outcome);
|
|
518
|
+
}
|
|
387
519
|
}
|
|
388
520
|
const requests = [];
|
|
389
521
|
const consoleErrors = [];
|
|
@@ -393,12 +525,21 @@ function collect(emitted, actions, unsettled) {
|
|
|
393
525
|
const url = evidence.payload["url"];
|
|
394
526
|
if (typeof method !== "string" || typeof url !== "string")
|
|
395
527
|
continue;
|
|
528
|
+
const requestId = evidence.requestId;
|
|
529
|
+
const pairedOutcome = (requestId === null ? undefined : outcomeByRequestId.get(requestId)) ?? outcomeByUrl.get(url);
|
|
530
|
+
const wasPaired = (requestId !== null && pairedRequestIds.has(requestId)) || pairedUrls.has(url);
|
|
531
|
+
// A recognised outcome from the paired terminal event wins; no terminal
|
|
532
|
+
// event at all means the request was still in flight when the drain
|
|
533
|
+
// happened, so "pending"; a terminal event that exists but names no
|
|
534
|
+
// recognisable outcome (an older `descry-runtime` build) leaves the
|
|
535
|
+
// field undefined rather than guessing — `isFailedRequest`'s documented
|
|
536
|
+
// fallback is what handles that case, not this function.
|
|
537
|
+
const outcome = pairedOutcome ?? (wasPaired ? undefined : "pending");
|
|
396
538
|
requests.push({
|
|
397
539
|
method,
|
|
398
540
|
url,
|
|
399
|
-
status: (
|
|
400
|
-
|
|
401
|
-
null,
|
|
541
|
+
status: (requestId === null ? undefined : statusByRequestId.get(requestId)) ?? statusByUrl.get(url) ?? null,
|
|
542
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
402
543
|
stackText: renderStack(evidence.stackTrace),
|
|
403
544
|
stackTrace: evidence.stackTrace,
|
|
404
545
|
scriptUrlMapping: scriptUrlOutcomesOf(evidence.payload),
|
|
@@ -424,6 +565,22 @@ function collect(emitted, actions, unsettled) {
|
|
|
424
565
|
}
|
|
425
566
|
return { actions: [...actions], consoleErrors, requests, unsettled: [...unsettled] };
|
|
426
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Reads a terminal event's `outcome` off an untyped payload.
|
|
570
|
+
*
|
|
571
|
+
* Validated rather than cast, exactly like `scriptUrlOutcomesOf` below: this
|
|
572
|
+
* crosses a package boundary — the field is `@descryy/runtime-browser`'s to
|
|
573
|
+
* set, and this build does not depend on that package's types — so an
|
|
574
|
+
* absent or unrecognised value must degrade to "no outcome reported", never
|
|
575
|
+
* be cast through as one of the three known literals it might not actually
|
|
576
|
+
* be.
|
|
577
|
+
*/
|
|
578
|
+
function requestOutcomeOf(payload) {
|
|
579
|
+
const raw = payload["outcome"];
|
|
580
|
+
if (raw === "answered" || raw === "unanswered" || raw === "pending")
|
|
581
|
+
return raw;
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
427
584
|
/**
|
|
428
585
|
* Reads the collector's `scriptUrlMapping` off an untyped payload.
|
|
429
586
|
*
|