@continuum8032/playwright-ai-tools 0.2.0-oidc-bootstrap.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.
@@ -0,0 +1,205 @@
1
+ // src/trace.ts
2
+ import { readFile } from "fs/promises";
3
+ import { inflateRawSync } from "zlib";
4
+ import path from "path";
5
+ function parseEvents(raw) {
6
+ const trimmed = raw.trim();
7
+ if (!trimmed) return [];
8
+ try {
9
+ const parsed = JSON.parse(trimmed);
10
+ if (Array.isArray(parsed)) return parsed;
11
+ if (Array.isArray(parsed.events)) return parsed.events;
12
+ if (Array.isArray(parsed.traceEvents)) return parsed.traceEvents;
13
+ return [parsed];
14
+ } catch {
15
+ return trimmed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
16
+ }
17
+ }
18
+ function isZip(buffer, filePath) {
19
+ return path.extname(filePath).toLowerCase() === ".zip" || buffer.readUInt32LE(0) === 67324752;
20
+ }
21
+ function isTextTraceEntry(name) {
22
+ const lower = name.toLowerCase();
23
+ if (lower.startsWith("resources/")) return false;
24
+ return lower.endsWith(".trace") || lower.endsWith(".network") || lower.endsWith(".json") || lower.endsWith(".jsonl") || lower.includes("trace") || lower.includes("network");
25
+ }
26
+ function findEndOfCentralDirectory(buffer) {
27
+ for (let index = buffer.length - 22; index >= 0; index -= 1) {
28
+ if (buffer.readUInt32LE(index) === 101010256) return index;
29
+ }
30
+ throw new Error("ZIP central directory not found in trace artifact");
31
+ }
32
+ function extractZipTexts(buffer) {
33
+ const eocd = findEndOfCentralDirectory(buffer);
34
+ const totalEntries = buffer.readUInt16LE(eocd + 10);
35
+ let offset = buffer.readUInt32LE(eocd + 16);
36
+ const texts = [];
37
+ for (let entry = 0; entry < totalEntries; entry += 1) {
38
+ if (buffer.readUInt32LE(offset) !== 33639248) break;
39
+ const method = buffer.readUInt16LE(offset + 10);
40
+ const compressedSize = buffer.readUInt32LE(offset + 20);
41
+ const nameLength = buffer.readUInt16LE(offset + 28);
42
+ const extraLength = buffer.readUInt16LE(offset + 30);
43
+ const commentLength = buffer.readUInt16LE(offset + 32);
44
+ const localOffset = buffer.readUInt32LE(offset + 42);
45
+ const name = buffer.subarray(offset + 46, offset + 46 + nameLength).toString("utf8");
46
+ offset += 46 + nameLength + extraLength + commentLength;
47
+ if (!isTextTraceEntry(name) || buffer.readUInt32LE(localOffset) !== 67324752) continue;
48
+ const localNameLength = buffer.readUInt16LE(localOffset + 26);
49
+ const localExtraLength = buffer.readUInt16LE(localOffset + 28);
50
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength;
51
+ const compressed = buffer.subarray(dataStart, dataStart + compressedSize);
52
+ if (method === 0) {
53
+ texts.push(compressed.toString("utf8"));
54
+ } else if (method === 8) {
55
+ texts.push(inflateRawSync(compressed).toString("utf8"));
56
+ }
57
+ }
58
+ return texts;
59
+ }
60
+ async function readTraceTexts(filePath) {
61
+ const buffer = await readFile(filePath);
62
+ if (isZip(buffer, filePath)) {
63
+ return extractZipTexts(buffer);
64
+ }
65
+ return [buffer.toString("utf8")];
66
+ }
67
+ function timestamp(event) {
68
+ return event.wallTime || event.timestamp || event.time;
69
+ }
70
+ function actionFromEvent(event) {
71
+ const name = event.apiName || event.method || event.action;
72
+ if (!name) return null;
73
+ return {
74
+ name: String(name),
75
+ selector: event.selector || event.params?.selector,
76
+ url: event.url || event.params?.url,
77
+ timestamp: timestamp(event),
78
+ pageId: event.pageId
79
+ };
80
+ }
81
+ function consoleFromEvent(event) {
82
+ const level = String(event.level || event.messageType || "").toLowerCase();
83
+ const text = event.text || event.message || event.args?.join?.(" ");
84
+ if (!text || !["error", "warning", "warn"].includes(level)) return null;
85
+ return {
86
+ level: level === "warn" ? "warning" : level,
87
+ text: String(text),
88
+ url: event.location?.url || event.url,
89
+ lineNumber: event.location?.lineNumber,
90
+ columnNumber: event.location?.columnNumber,
91
+ timestamp: timestamp(event)
92
+ };
93
+ }
94
+ function networkFromEvent(event) {
95
+ const snapshot = event.snapshot || event;
96
+ const request = snapshot.request || event.request || {};
97
+ const response = snapshot.response || event.response || {};
98
+ const url = request.url || event.url;
99
+ const status = Number(response.status ?? event.status ?? 0);
100
+ const errorText = event.errorText || snapshot.errorText || response.errorText;
101
+ if (!url || status < 400 && !errorText) return null;
102
+ return {
103
+ url: String(url),
104
+ method: request.method || event.method,
105
+ status: status || void 0,
106
+ statusText: response.statusText,
107
+ errorText,
108
+ timestamp: timestamp(event)
109
+ };
110
+ }
111
+ function trimLastActions(actions) {
112
+ return actions.slice(-10);
113
+ }
114
+ async function parseTraceArtifact(filePath) {
115
+ const texts = await readTraceTexts(filePath);
116
+ const events = texts.flatMap(parseEvents);
117
+ const summary = {
118
+ consoleErrors: [],
119
+ networkFailures: [],
120
+ lastActions: [],
121
+ artifactRefs: [{ type: "trace", path: filePath }]
122
+ };
123
+ for (const event of events) {
124
+ if (!event || typeof event !== "object") continue;
125
+ if (event.type === "context-options" || event.browserName || event.viewport) {
126
+ const { type: _type, ...context } = event;
127
+ summary.browserContext = { ...summary.browserContext || {}, ...context };
128
+ }
129
+ if (event.type === "before" || event.type === "action") {
130
+ const action = actionFromEvent(event);
131
+ if (action) summary.lastActions = trimLastActions([...summary.lastActions, action]);
132
+ }
133
+ if (event.type === "after" && event.result) {
134
+ summary.page = {
135
+ ...summary.page || {},
136
+ url: event.result.url || summary.page?.url,
137
+ title: event.result.title || summary.page?.title
138
+ };
139
+ }
140
+ const consoleEvent = consoleFromEvent(event);
141
+ if (consoleEvent) summary.consoleErrors.push(consoleEvent);
142
+ const networkEvent = networkFromEvent(event);
143
+ if (networkEvent) summary.networkFailures.push(networkEvent);
144
+ const snapshot = event.snapshot?.html || event.snapshot?.body || event.html;
145
+ if (event.type === "page-snapshot" && snapshot) {
146
+ summary.pageSnapshot = String(snapshot);
147
+ summary.page = { ...summary.page || {}, snapshot: summary.pageSnapshot };
148
+ }
149
+ }
150
+ return summary;
151
+ }
152
+
153
+ // src/evidence.ts
154
+ import path2 from "path";
155
+ function isTraceArtifact(artifact) {
156
+ const haystack = `${artifact.type} ${artifact.path} ${artifact.contentType || ""}`.toLowerCase();
157
+ return haystack.includes("trace") || haystack.includes("jsonl") || haystack.includes("application/zip");
158
+ }
159
+ function resolveArtifactPath(artifactPath, root) {
160
+ if (path2.isAbsolute(artifactPath)) return artifactPath;
161
+ return path2.resolve(root || process.cwd(), artifactPath);
162
+ }
163
+ async function enrichFailureWithArtifacts(failure, options = {}) {
164
+ const artifacts = failure.artifacts || [];
165
+ const artifactRefs = [...failure.artifactRefs || [], ...artifacts];
166
+ let enriched = { ...failure, artifactRefs };
167
+ for (const artifact of artifacts.filter(isTraceArtifact)) {
168
+ let trace;
169
+ try {
170
+ trace = await parseTraceArtifact(resolveArtifactPath(artifact.path, options.artifactRoot));
171
+ } catch (error) {
172
+ enriched = {
173
+ ...enriched,
174
+ artifactRefs: [
175
+ ...artifactRefs,
176
+ {
177
+ ...artifact,
178
+ metadata: {
179
+ ...artifact.metadata || {},
180
+ parseError: error?.message || String(error)
181
+ }
182
+ }
183
+ ]
184
+ };
185
+ continue;
186
+ }
187
+ enriched = {
188
+ ...enriched,
189
+ trace,
190
+ browserContext: trace.browserContext || enriched.browserContext,
191
+ page: trace.page || enriched.page,
192
+ pageSnapshot: trace.pageSnapshot || enriched.pageSnapshot,
193
+ consoleErrors: [...enriched.consoleErrors || [], ...trace.consoleErrors],
194
+ networkFailures: [...enriched.networkFailures || [], ...trace.networkFailures],
195
+ lastActions: [...enriched.lastActions || [], ...trace.lastActions].slice(-10),
196
+ artifactRefs: [...artifactRefs, ...trace.artifactRefs]
197
+ };
198
+ }
199
+ return enriched;
200
+ }
201
+
202
+ export {
203
+ parseTraceArtifact,
204
+ enrichFailureWithArtifacts
205
+ };