@empiricalrun/test-gen 0.79.2 → 0.79.3

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/agent/chat/exports.d.ts +1 -0
  3. package/dist/agent/chat/exports.d.ts.map +1 -1
  4. package/dist/agent/chat/exports.js +3 -1
  5. package/dist/agent/chat/index.js +1 -1
  6. package/dist/agent/chat/prompt/repo.d.ts.map +1 -1
  7. package/dist/agent/chat/prompt/repo.js +4 -3
  8. package/dist/agent/fast-triage/index.d.ts +8 -0
  9. package/dist/agent/fast-triage/index.d.ts.map +1 -0
  10. package/dist/agent/fast-triage/index.js +51 -0
  11. package/dist/agent/index.d.ts +2 -1
  12. package/dist/agent/index.d.ts.map +1 -1
  13. package/dist/agent/index.js +4 -1
  14. package/dist/agent/triage/index.js +2 -2
  15. package/dist/bin/index.js +5 -3
  16. package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
  17. package/dist/bin/utils/platform/web/index.js +3 -2
  18. package/dist/dashboard/client.d.ts +11 -1
  19. package/dist/dashboard/client.d.ts.map +1 -1
  20. package/dist/dashboard/client.js +22 -9
  21. package/dist/file-info/adapters/github/index.d.ts.map +1 -1
  22. package/dist/file-info/adapters/github/index.js +1 -1
  23. package/dist/generate-summary/frame-sampling.d.ts +12 -0
  24. package/dist/generate-summary/frame-sampling.d.ts.map +1 -0
  25. package/dist/generate-summary/frame-sampling.js +72 -0
  26. package/dist/generate-summary/generate-error-stack-summary.d.ts +11 -0
  27. package/dist/generate-summary/generate-error-stack-summary.d.ts.map +1 -0
  28. package/dist/generate-summary/generate-error-stack-summary.js +44 -0
  29. package/dist/generate-summary/generate-failed-step-screenshot-diff-summary.d.ts +58 -0
  30. package/dist/generate-summary/generate-failed-step-screenshot-diff-summary.d.ts.map +1 -0
  31. package/dist/generate-summary/generate-failed-step-screenshot-diff-summary.js +460 -0
  32. package/dist/generate-summary/generate-grouped-summary.d.ts +18 -0
  33. package/dist/generate-summary/generate-grouped-summary.d.ts.map +1 -0
  34. package/dist/generate-summary/generate-grouped-summary.js +91 -0
  35. package/dist/generate-summary/merge-summary.d.ts +16 -0
  36. package/dist/generate-summary/merge-summary.d.ts.map +1 -0
  37. package/dist/generate-summary/merge-summary.js +46 -0
  38. package/dist/generate-summary/pick-videos-for-comparison.d.ts +9 -0
  39. package/dist/generate-summary/pick-videos-for-comparison.d.ts.map +1 -0
  40. package/dist/generate-summary/pick-videos-for-comparison.js +54 -0
  41. package/dist/telemetry/index.d.ts.map +1 -1
  42. package/dist/telemetry/index.js +3 -1
  43. package/dist/tools/definitions/delete-file.js +1 -1
  44. package/dist/tools/definitions/grep.d.ts.map +1 -1
  45. package/dist/tools/definitions/grep.js +1 -1
  46. package/dist/tools/definitions/rename-file.js +2 -2
  47. package/dist/tools/definitions/safe-bash.d.ts.map +1 -1
  48. package/dist/tools/definitions/safe-bash.js +10 -8
  49. package/dist/tools/definitions/str_replace_editor.d.ts.map +1 -1
  50. package/dist/tools/definitions/str_replace_editor.js +12 -4
  51. package/dist/tools/definitions/trace-dot-zip.d.ts +7 -0
  52. package/dist/tools/definitions/trace-dot-zip.d.ts.map +1 -0
  53. package/dist/tools/definitions/trace-dot-zip.js +16 -0
  54. package/dist/tools/definitions/utils.js +1 -1
  55. package/dist/tools/delete-file/index.d.ts.map +1 -1
  56. package/dist/tools/delete-file/index.js +9 -6
  57. package/dist/tools/diagnosis-fetcher.d.ts.map +1 -1
  58. package/dist/tools/diagnosis-fetcher.js +40 -2
  59. package/dist/tools/fetch-file/index.d.ts.map +1 -1
  60. package/dist/tools/fetch-file/index.js +102 -3
  61. package/dist/tools/file-operations/index.d.ts.map +1 -1
  62. package/dist/tools/file-operations/index.js +7 -5
  63. package/dist/tools/file-operations/shared/helpers.d.ts +13 -0
  64. package/dist/tools/file-operations/shared/helpers.d.ts.map +1 -1
  65. package/dist/tools/file-operations/shared/helpers.js +24 -0
  66. package/dist/tools/file-operations/view/index.d.ts.map +1 -1
  67. package/dist/tools/file-operations/view/index.js +9 -3
  68. package/dist/tools/grep/index.d.ts.map +1 -1
  69. package/dist/tools/grep/index.js +7 -2
  70. package/dist/tools/index.d.ts +1 -1
  71. package/dist/tools/index.d.ts.map +1 -1
  72. package/dist/tools/index.js +6 -6
  73. package/dist/tools/merge-conflicts/index.d.ts.map +1 -1
  74. package/dist/tools/merge-conflicts/index.js +8 -8
  75. package/dist/tools/rename-file/index.d.ts.map +1 -1
  76. package/dist/tools/rename-file/index.js +11 -7
  77. package/dist/tools/run-test.d.ts.map +1 -1
  78. package/dist/tools/run-test.js +12 -7
  79. package/dist/tools/safe-bash/index.d.ts.map +1 -1
  80. package/dist/tools/safe-bash/index.js +18 -2
  81. package/dist/tools/test-gen-browser.d.ts.map +1 -1
  82. package/dist/tools/test-gen-browser.js +12 -9
  83. package/dist/tools/trace-dot-zip/index.d.ts +3 -1
  84. package/dist/tools/trace-dot-zip/index.d.ts.map +1 -1
  85. package/dist/tools/trace-dot-zip/index.js +8 -20
  86. package/dist/tools/trace-dot-zip/utils/console-trace.d.ts.map +1 -1
  87. package/dist/tools/trace-dot-zip/utils/console-trace.js +11 -5
  88. package/dist/tools/trace-dot-zip/utils/extract-screenshots.d.ts +27 -0
  89. package/dist/tools/trace-dot-zip/utils/extract-screenshots.d.ts.map +1 -0
  90. package/dist/tools/trace-dot-zip/utils/extract-screenshots.js +128 -0
  91. package/dist/tools/trace-dot-zip/utils/extract-steps.d.ts +12 -0
  92. package/dist/tools/trace-dot-zip/utils/extract-steps.d.ts.map +1 -0
  93. package/dist/tools/trace-dot-zip/utils/extract-steps.js +130 -0
  94. package/dist/tools/trace-dot-zip/utils/extract-zip.d.ts +13 -16
  95. package/dist/tools/trace-dot-zip/utils/extract-zip.d.ts.map +1 -1
  96. package/dist/tools/trace-dot-zip/utils/extract-zip.js +27 -167
  97. package/dist/tools/trace-dot-zip/utils/network-trace.d.ts.map +1 -1
  98. package/dist/tools/trace-dot-zip/utils/network-trace.js +136 -105
  99. package/dist/trace-utils/cli.d.ts +3 -0
  100. package/dist/trace-utils/cli.d.ts.map +1 -0
  101. package/dist/trace-utils/cli.js +302 -0
  102. package/dist/trace-utils/console.d.ts +11 -0
  103. package/dist/trace-utils/console.d.ts.map +1 -0
  104. package/dist/trace-utils/console.js +74 -0
  105. package/dist/trace-utils/dom-snapshot.d.ts +19 -0
  106. package/dist/trace-utils/dom-snapshot.d.ts.map +1 -0
  107. package/dist/trace-utils/dom-snapshot.js +328 -0
  108. package/dist/trace-utils/index.d.ts +8 -0
  109. package/dist/trace-utils/index.d.ts.map +1 -1
  110. package/dist/trace-utils/index.js +19 -1
  111. package/dist/trace-utils/network.d.ts +16 -0
  112. package/dist/trace-utils/network.d.ts.map +1 -0
  113. package/dist/trace-utils/network.js +178 -0
  114. package/dist/trace-utils/normalize-trace-url.d.ts +2 -0
  115. package/dist/trace-utils/normalize-trace-url.d.ts.map +1 -0
  116. package/dist/trace-utils/normalize-trace-url.js +15 -0
  117. package/dist/trace-utils/screenshots.d.ts +24 -0
  118. package/dist/trace-utils/screenshots.d.ts.map +1 -0
  119. package/dist/trace-utils/screenshots.js +197 -0
  120. package/dist/trace-utils/steps.d.ts +10 -0
  121. package/dist/trace-utils/steps.d.ts.map +1 -0
  122. package/dist/trace-utils/steps.js +126 -0
  123. package/dist/trace-utils/types.d.ts +51 -0
  124. package/dist/trace-utils/types.d.ts.map +1 -0
  125. package/dist/trace-utils/types.js +2 -0
  126. package/dist/utils/playwright-report-parser.d.ts +1 -12
  127. package/dist/utils/playwright-report-parser.d.ts.map +1 -1
  128. package/dist/utils/playwright-report-parser.js +8 -136
  129. package/dist/video-core/index.d.ts.map +1 -1
  130. package/dist/video-core/index.js +17 -33
  131. package/package.json +12 -6
  132. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateFailedStepScreenshotDiffSummary = exports.getScreenshotsInFailedStepWindow = exports.getFailedStep = exports.findSimilarActionFromList = void 0;
7
+ const llm_1 = require("@empiricalrun/llm");
8
+ const r2_uploader_1 = require("@empiricalrun/r2-uploader");
9
+ const trace_utils_1 = require("@empiricalrun/test-gen/trace-utils");
10
+ // import { waitUntil } from "@vercel/functions";
11
+ const buffer_1 = require("buffer");
12
+ // import fs from "fs";
13
+ const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
14
+ // import prompt from "../../../../prompts/video-diff-summary-prompt.handlebars";
15
+ const trace_utils_2 = require("../trace-utils");
16
+ const frame_sampling_1 = require("./frame-sampling");
17
+ const responseFormat = {
18
+ type: "json_schema",
19
+ json_schema: {
20
+ name: "screenshot-difference-with-failure-reason",
21
+ strict: true,
22
+ schema: {
23
+ type: "object",
24
+ required: ["difference_in_screenshots", "reason_for_test_failure"],
25
+ properties: {
26
+ difference_in_screenshots: {
27
+ type: "string",
28
+ description: "what has changed between the successful run and failed run screenshots",
29
+ },
30
+ reason_for_test_failure: {
31
+ type: "string",
32
+ description: "reason for test failure",
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ },
38
+ };
39
+ const convertBufferToBase64 = (imageBuffer, mimeType) => {
40
+ const base64Image = imageBuffer.toString("base64");
41
+ const dataUrl = `data:${mimeType};base64,${base64Image}`;
42
+ return dataUrl;
43
+ };
44
+ const uploadScreenshotsToR2 = async ({ successScreenshots, failureScreenshots, testRunId, project, test, }) => {
45
+ try {
46
+ const file = {
47
+ buffer: buffer_1.Buffer.from(JSON.stringify({
48
+ success: successScreenshots.map((screenshot) => {
49
+ return screenshot.base64;
50
+ }),
51
+ failure: failureScreenshots.map((screenshot) => {
52
+ return screenshot.base64;
53
+ }),
54
+ }), "utf-8"),
55
+ fileName: `test-case-${test.testCaseId}-${test.slug}.json`,
56
+ mimeType: "application/json",
57
+ };
58
+ const folderName = process.env.NODE_ENV === "production"
59
+ ? "visual-comparison-screenshots"
60
+ : "visual-comparison-screenshots-development";
61
+ const fileUrls = await (0, r2_uploader_1.uploadInMemoryFiles)({
62
+ files: [file],
63
+ destinationDir: `${project.repo_name.replace("-tests", "")}/${testRunId}/${folderName}`,
64
+ uploadBucket: "test-report",
65
+ accountId: process.env.R2_ACCOUNT_ID,
66
+ accessKeyId: process.env.R2_ACCESS_KEY_ID,
67
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
68
+ });
69
+ return fileUrls;
70
+ }
71
+ catch (e) {
72
+ console.error(`[testRunId - ${testRunId}] uploadScreenshotsToR2 failed`, e);
73
+ return {};
74
+ }
75
+ };
76
+ const findSimilarActionFromList = ({ actionList, actionToFind, }) => {
77
+ const actionStack = actionToFind?.stack?.[0];
78
+ let matchingAction = actionList.find((currentAction) => {
79
+ const currentStack = currentAction?.stack?.[0];
80
+ return (currentStack?.file === actionStack?.file &&
81
+ currentStack?.line === actionStack?.line &&
82
+ currentStack?.column === actionStack?.column &&
83
+ currentAction?.apiName === actionToFind?.apiName &&
84
+ (0, lodash_isequal_1.default)(currentAction?.params, actionToFind?.params));
85
+ });
86
+ if (matchingAction) {
87
+ return matchingAction;
88
+ }
89
+ matchingAction = actionList.find((currentAction) => {
90
+ const currentStack = currentAction?.stack?.[0];
91
+ return (currentStack?.file === actionStack?.file &&
92
+ currentAction?.apiName === actionToFind?.apiName &&
93
+ (0, lodash_isequal_1.default)(currentAction?.params, actionToFind?.params));
94
+ });
95
+ if (matchingAction) {
96
+ return matchingAction;
97
+ }
98
+ matchingAction = actionList.find((currentAction) => {
99
+ const currentStack = currentAction?.stack?.[0];
100
+ return (currentStack?.file === actionStack?.file &&
101
+ currentStack?.line === actionStack?.line &&
102
+ currentStack?.column === actionStack?.column &&
103
+ currentAction?.apiName === actionToFind?.apiName);
104
+ });
105
+ return matchingAction;
106
+ };
107
+ exports.findSimilarActionFromList = findSimilarActionFromList;
108
+ const getFailedStep = async ({ zipUrl, sendParentForBeforeAction = true, }) => {
109
+ const allTestActions = [];
110
+ const testTraceFile = "test.trace";
111
+ await (0, trace_utils_2.extractFileFromZipFromUrl)({
112
+ zipUrl,
113
+ fileNames: [testTraceFile],
114
+ chunkProcessor: (_, fileName) => {
115
+ let testTraceTempBuffer = "";
116
+ return (chunk) => {
117
+ switch (fileName) {
118
+ case testTraceFile: {
119
+ testTraceTempBuffer += chunk;
120
+ let lines = testTraceTempBuffer.split("\n");
121
+ lines.slice(0, -1).forEach((line) => {
122
+ const parsedEvent = JSON.parse(line);
123
+ allTestActions.push(parsedEvent);
124
+ });
125
+ testTraceTempBuffer = lines[lines.length - 1];
126
+ break;
127
+ }
128
+ default:
129
+ break;
130
+ }
131
+ };
132
+ },
133
+ });
134
+ const totalActions = allTestActions.length;
135
+ let lastBeforeAction;
136
+ let lastBeforeActionParent;
137
+ for (let actionIndex = 0; actionIndex < totalActions; actionIndex++) {
138
+ const currentAction = allTestActions[actionIndex];
139
+ if (currentAction.type === "before") {
140
+ lastBeforeAction = currentAction;
141
+ }
142
+ if (currentAction.type === "error") {
143
+ break;
144
+ }
145
+ }
146
+ // This handles cases where the failed step is inside a locator
147
+ // patch method of playwright-utils, and our intent is to find
148
+ // the parent action -- which is the caller in the actual test file
149
+ const isPlaywrightUtilsPatch = lastBeforeAction?.stack?.some((stackFrame) => stackFrame.file.includes("@empiricalrun/playwright-utils"));
150
+ if (isPlaywrightUtilsPatch && lastBeforeAction?.parentId) {
151
+ lastBeforeActionParent = allTestActions.find((action) => action.type === "before" &&
152
+ action.callId === lastBeforeAction?.parentId);
153
+ }
154
+ if (!lastBeforeAction) {
155
+ throw new Error("No before action found before error");
156
+ }
157
+ // Return parent action if requested and available, otherwise return the last before action
158
+ return sendParentForBeforeAction && lastBeforeActionParent
159
+ ? lastBeforeActionParent
160
+ : lastBeforeAction;
161
+ };
162
+ exports.getFailedStep = getFailedStep;
163
+ const getScreenshotsInFailedStepWindow = async ({ failedStep, testRunId, zipUrl, isSuccessRun = false, }) => {
164
+ try {
165
+ const allTestActions = [];
166
+ const allScreenshots = [];
167
+ const stepIdToCallIdMap = {};
168
+ const detailedBeforeTraceActions = [];
169
+ const wallTimeToCallIdMap = {};
170
+ const testTraceFile = "test.trace";
171
+ const fileNames = await (0, trace_utils_2.getFilenamesInZip)(zipUrl, {
172
+ recursive: false,
173
+ });
174
+ const detailedTraceFileList = fileNames.filter((fileName) => fileName.includes("trace.trace"));
175
+ await (0, trace_utils_2.extractFileFromZipFromUrl)({
176
+ zipUrl,
177
+ fileNames: [testTraceFile, ...detailedTraceFileList],
178
+ chunkProcessor: (_, fileName) => {
179
+ let testTraceTempBuffer = "";
180
+ let detailedTraceTempBuffer = "";
181
+ return (chunk) => {
182
+ if (fileName === testTraceFile) {
183
+ testTraceTempBuffer += chunk;
184
+ let lines = testTraceTempBuffer.split("\n");
185
+ lines.slice(0, -1).forEach((line) => {
186
+ const parsedEvent = JSON.parse(line);
187
+ if (parsedEvent.type === "before" ||
188
+ parsedEvent.type === "after") {
189
+ allTestActions.push(parsedEvent);
190
+ }
191
+ });
192
+ testTraceTempBuffer = lines[lines.length - 1];
193
+ }
194
+ else if (fileName.includes("trace.trace")) {
195
+ detailedTraceTempBuffer += chunk;
196
+ let lines = detailedTraceTempBuffer.split("\n");
197
+ lines.slice(0, -1).forEach((line) => {
198
+ const parsedEvent = JSON.parse(line);
199
+ if (parsedEvent.type === "screencast-frame") {
200
+ allScreenshots.push({
201
+ ...parsedEvent,
202
+ sha1: `resources/${parsedEvent.sha1}`,
203
+ });
204
+ }
205
+ else if (parsedEvent.type === "before") {
206
+ detailedBeforeTraceActions.push(parsedEvent);
207
+ if (parsedEvent.stepId) {
208
+ stepIdToCallIdMap[parsedEvent.stepId] =
209
+ parsedEvent;
210
+ }
211
+ if (parsedEvent.wallTime) {
212
+ wallTimeToCallIdMap[parsedEvent.wallTime] =
213
+ parsedEvent;
214
+ }
215
+ }
216
+ });
217
+ detailedTraceTempBuffer = lines[lines.length - 1];
218
+ }
219
+ };
220
+ },
221
+ });
222
+ const beforeActions = allTestActions.filter((action) => {
223
+ const isBefore = action.type === "before";
224
+ return isBefore;
225
+ });
226
+ let currentFailedStep = (0, exports.findSimilarActionFromList)({
227
+ actionList: beforeActions,
228
+ actionToFind: failedStep,
229
+ });
230
+ // if the callId is a step then that mapping doesnt exist inside the detail trace.
231
+ // we need to find the right step inside it to map it
232
+ // currently assuming test.step can only be in case playwright utils patched apis
233
+ if (currentFailedStep?.callId.includes("test.step")) {
234
+ currentFailedStep =
235
+ beforeActions.find((a) => a.parentId === currentFailedStep.callId) ||
236
+ currentFailedStep;
237
+ }
238
+ // console.log("allscreenshots", allScreenshots.slice(0, 3));
239
+ const failedStepIndex = beforeActions.findIndex((a) => {
240
+ return a.callId === currentFailedStep.callId;
241
+ });
242
+ console.log("failedStepIndex", failedStepIndex);
243
+ console.log("beforeActions", beforeActions[failedStepIndex]);
244
+ let stepEndTime = allTestActions.find((action) => {
245
+ return (action.type === "after" && action.callId === currentFailedStep.callId);
246
+ }).endTime;
247
+ let lastActionCapturedInTestTrace = currentFailedStep;
248
+ let lastActionCapturedInDetailTrace = (stepIdToCallIdMap[currentFailedStep.callId] ||
249
+ wallTimeToCallIdMap[currentFailedStep.wallTime]);
250
+ if (!lastActionCapturedInDetailTrace) {
251
+ const actionsTakenTillLastFailedStepInReverseOrder = beforeActions
252
+ .slice(0, failedStepIndex)
253
+ .reverse();
254
+ const totalReversedActions = actionsTakenTillLastFailedStepInReverseOrder.length;
255
+ for (let actionIndex = 0; actionIndex < totalReversedActions; actionIndex++) {
256
+ const action = actionsTakenTillLastFailedStepInReverseOrder[actionIndex];
257
+ const correspondingDetailTraceAction = stepIdToCallIdMap[action.callId] ||
258
+ wallTimeToCallIdMap[action.wallTime];
259
+ if (correspondingDetailTraceAction) {
260
+ lastActionCapturedInTestTrace = action;
261
+ lastActionCapturedInDetailTrace = correspondingDetailTraceAction;
262
+ }
263
+ if (lastActionCapturedInDetailTrace) {
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ const defaultWindowSize = 2000;
269
+ const pageIdForFailedStep = lastActionCapturedInDetailTrace.pageId;
270
+ const stepStartTime = isSuccessRun
271
+ ? lastActionCapturedInTestTrace.startTime - defaultWindowSize / 2
272
+ : lastActionCapturedInTestTrace.startTime;
273
+ const endTime = isSuccessRun
274
+ ? stepEndTime + defaultWindowSize
275
+ : stepEndTime;
276
+ // console.log("stepStartTime", stepStartTime);
277
+ // console.log("endTime", endTime);
278
+ const screenshotsForPage = allScreenshots.filter((screenshot) => {
279
+ return screenshot.pageId === pageIdForFailedStep;
280
+ });
281
+ allScreenshots.sort((a, b) => a.timestamp - b.timestamp);
282
+ const availableScreenshotTimestamps = screenshotsForPage.map((s) => s.timestamp);
283
+ let screenshotsToConsider = allScreenshots.filter((screenshot) => {
284
+ return (screenshot.pageId === pageIdForFailedStep &&
285
+ screenshot.timestamp <= endTime &&
286
+ screenshot.timestamp >= stepStartTime);
287
+ });
288
+ if (screenshotsToConsider.length === 0) {
289
+ const lastScreenshotTimestamp = availableScreenshotTimestamps[availableScreenshotTimestamps.length - 1];
290
+ // if the available screenshot timestamps are a lot less than the step start time
291
+ // take a window of last 2 seconds from the end of the screenshot timeline
292
+ if (lastScreenshotTimestamp < stepStartTime) {
293
+ screenshotsToConsider = allScreenshots.filter((screenshot) => {
294
+ return (screenshot.pageId === pageIdForFailedStep &&
295
+ screenshot.timestamp <= lastScreenshotTimestamp &&
296
+ screenshot.timestamp >= lastScreenshotTimestamp - defaultWindowSize);
297
+ });
298
+ }
299
+ else {
300
+ // if the failed step is in the middle of screenshot timestamp but no corresponding image is found
301
+ // take a window of 4 seconds before and after the failed step
302
+ screenshotsToConsider = allScreenshots.filter((screenshot) => {
303
+ return (screenshot.pageId === pageIdForFailedStep &&
304
+ screenshot.timestamp <= endTime + defaultWindowSize / 2 &&
305
+ screenshot.timestamp >= stepStartTime - defaultWindowSize / 2);
306
+ });
307
+ }
308
+ }
309
+ const imageBufferMap = {};
310
+ await (0, trace_utils_2.extractFileFromZipFromUrl)({
311
+ zipUrl,
312
+ fileNames: screenshotsToConsider.map((s) => s.sha1),
313
+ chunkProcessor: (_, fileName) => {
314
+ imageBufferMap[fileName] = imageBufferMap[fileName] || [];
315
+ return (chunk) => {
316
+ if (chunk) {
317
+ imageBufferMap[fileName].push(chunk);
318
+ }
319
+ };
320
+ },
321
+ });
322
+ const screenshotsWithBase64 = screenshotsToConsider.map((screenshot) => {
323
+ const imageBuffer = buffer_1.Buffer.concat(imageBufferMap[screenshot.sha1]);
324
+ const base64 = convertBufferToBase64(imageBuffer, "image/jpeg");
325
+ return {
326
+ metadata: { ...screenshot, base64 },
327
+ image: imageBuffer.toString("base64"),
328
+ };
329
+ });
330
+ // console.log("SCREENSHOT", screenshotsWithBase64.length);
331
+ const images = await (0, trace_utils_1.deduplicateImages)({
332
+ base64Images: screenshotsWithBase64,
333
+ threshold: 0.001,
334
+ logPrefix: `TestRun: ${testRunId}`,
335
+ });
336
+ // console.log("DEDUPED IMAGES", images.length);
337
+ return images.map((i) => i.metadata);
338
+ }
339
+ catch (e) {
340
+ console.warn(`Error extracting screenshots for testRunId - "${testRunId}", zipUrl - "${zipUrl}"`, e?.message);
341
+ return [];
342
+ }
343
+ };
344
+ exports.getScreenshotsInFailedStepWindow = getScreenshotsInFailedStepWindow;
345
+ const generateFailedStepScreenshotDiffSummary = async ({ testRunId, test, modelConfig, trace, project, }) => {
346
+ if (!test.success?.trace && !test.failure?.trace) {
347
+ console.warn(`[testRunId - ${testRunId}][test - ${test.title}] No success and failure trace found for test, exiting early ...`);
348
+ return "";
349
+ }
350
+ if (!test.failure?.trace) {
351
+ console.warn(`[testRunId - ${testRunId}][test - ${test.title}] No failure trace found for test, exiting early ...`);
352
+ return "";
353
+ }
354
+ try {
355
+ const failedStep = await (0, exports.getFailedStep)({
356
+ testRunId,
357
+ zipUrl: test.failure.trace,
358
+ });
359
+ let failureScreenshots = await (0, exports.getScreenshotsInFailedStepWindow)({
360
+ failedStep,
361
+ testRunId,
362
+ zipUrl: test.failure.trace,
363
+ });
364
+ const totalFailureScreenshots = failureScreenshots?.length ?? 0;
365
+ console.log(`[testRunId - ${testRunId}][test - ${test.title}] has - "${totalFailureScreenshots}" failure screenshots`);
366
+ trace?.event({
367
+ name: "collected-failure-actions",
368
+ output: {
369
+ success: true,
370
+ data: { count: totalFailureScreenshots },
371
+ },
372
+ });
373
+ if (!totalFailureScreenshots) {
374
+ return "";
375
+ }
376
+ let successScreenshots = test.success.trace
377
+ ? await (0, exports.getScreenshotsInFailedStepWindow)({
378
+ failedStep,
379
+ testRunId,
380
+ zipUrl: test.success.trace,
381
+ isSuccessRun: true,
382
+ })
383
+ : [];
384
+ const totalSuccessScreenshots = successScreenshots?.length ?? 0;
385
+ console.log(`[testRunId - ${testRunId}][test - ${test.title}] has - "${totalSuccessScreenshots}" success screenshots`);
386
+ trace?.event({
387
+ name: "collected-success-actions",
388
+ output: {
389
+ success: true,
390
+ data: { count: totalSuccessScreenshots },
391
+ },
392
+ });
393
+ const totalScreenshots = totalSuccessScreenshots + totalFailureScreenshots;
394
+ const maxScreenshotsAllowed = 250;
395
+ if (totalScreenshots > maxScreenshotsAllowed) {
396
+ console.log(`[testRunId - ${testRunId}][testCaseId - ${test.testCaseId}] has - "${totalScreenshots}" screenshots, sampling to - "${maxScreenshotsAllowed}"`);
397
+ const sampledList = (0, frame_sampling_1.sampleCombinedList)(failureScreenshots, successScreenshots, maxScreenshotsAllowed);
398
+ failureScreenshots = sampledList.list1;
399
+ successScreenshots = sampledList.list2;
400
+ }
401
+ // waitUntil(
402
+ // uploadScreenshotsToR2({
403
+ // successScreenshots,
404
+ // failureScreenshots,
405
+ // testRunId,
406
+ // project,
407
+ // test,
408
+ // }),
409
+ // );
410
+ const modelProvider = modelConfig?.provider ?? "openai";
411
+ const model = modelConfig?.model ?? "gpt-4o";
412
+ const temperature = modelConfig?.temperature ?? 0.5;
413
+ const llm = new llm_1.LLM({
414
+ provider: modelProvider,
415
+ defaultModel: model,
416
+ });
417
+ const generationTrace = trace?.generation({
418
+ name: "generate-trace-screenshot-diff-summary",
419
+ model,
420
+ modelParameters: {
421
+ temperature,
422
+ },
423
+ });
424
+ const totalImageCount = successScreenshots.length + failureScreenshots.length;
425
+ // If there are too many images LLM failes to respond within alloted timeout
426
+ const imageDetail = totalImageCount > 40 ? "low" : "high";
427
+ const messages = (0, llm_1.compilePrompt)("prompt", {
428
+ successScreenshots: successScreenshots.map((s) => s.base64),
429
+ failureScreenshots: failureScreenshots.map((s) => s.base64),
430
+ errorStack: test.failure.stack,
431
+ }, {
432
+ imageDetail,
433
+ });
434
+ const llmResponse = await llm.createChatCompletion({
435
+ messages,
436
+ trace: generationTrace,
437
+ modelParameters: {
438
+ temperature,
439
+ },
440
+ responseFormat,
441
+ });
442
+ const parsedResponse = JSON.parse(llmResponse?.content);
443
+ generationTrace?.end({
444
+ output: {
445
+ videoDiffSummary: JSON.stringify(parsedResponse),
446
+ },
447
+ usage: {
448
+ input: llm.promptTokens,
449
+ output: llm.completionTokens,
450
+ unit: "TOKENS",
451
+ },
452
+ });
453
+ return parsedResponse?.reason_for_test_failure ?? "";
454
+ }
455
+ catch (e) {
456
+ console.error(`[testRunId - ${testRunId}][testCaseId - ${test.testCaseId}] Error generating trace screenshot diff summary`, e);
457
+ return "";
458
+ }
459
+ };
460
+ exports.generateFailedStepScreenshotDiffSummary = generateFailedStepScreenshotDiffSummary;
@@ -0,0 +1,18 @@
1
+ import { TraceClient } from "@empiricalrun/llm";
2
+ import { z } from "zod";
3
+ declare const groupedSummarySchema: z.ZodArray<z.ZodObject<{
4
+ testIds: z.ZodArray<z.ZodString>;
5
+ groupSummary: z.ZodString;
6
+ }, z.core.$strip>>;
7
+ export type GroupedSummarySchemaType = z.infer<typeof groupedSummarySchema>;
8
+ type ArgsT = {
9
+ testRunId: number;
10
+ tests: {
11
+ testId: string;
12
+ errorSummary: string;
13
+ }[];
14
+ trace?: TraceClient;
15
+ };
16
+ export declare const generateGroupedSummary: ({ testRunId, tests, trace, }: ArgsT) => Promise<GroupedSummarySchemaType | null>;
17
+ export {};
18
+ //# sourceMappingURL=generate-grouped-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-grouped-summary.d.ts","sourceRoot":"","sources":["../../src/generate-summary/generate-grouped-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,oBAAoB;;;kBAKzB,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA0C5E,KAAK,KAAK,GAAG;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClD,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AACF,eAAO,MAAM,sBAAsB,GAAU,8BAI1C,KAAK,KAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAgDjD,CAAC"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateGroupedSummary = void 0;
7
+ const llm_1 = require("@empiricalrun/llm");
8
+ const zod_1 = require("zod");
9
+ const group_summary_handlebars_1 = __importDefault(require("@/prompts/group-summary.handlebars"));
10
+ const groupedSummarySchema = zod_1.z.array(zod_1.z.object({
11
+ testIds: zod_1.z.array(zod_1.z.string()),
12
+ groupSummary: zod_1.z.string(),
13
+ }));
14
+ const getResponseFormat = (availableTestIds) => ({
15
+ type: "json_schema",
16
+ json_schema: {
17
+ name: "test-case-failure-grouped-summary",
18
+ strict: true,
19
+ schema: {
20
+ type: "object",
21
+ properties: {
22
+ groups: {
23
+ type: "array",
24
+ description: "array containing tests grouped by similar error summaries",
25
+ items: {
26
+ type: "object",
27
+ properties: {
28
+ testIds: {
29
+ type: "array",
30
+ description: "array of test ids which are grouped together",
31
+ items: {
32
+ type: "string",
33
+ description: "Unique identifier for the tests, provided by the user",
34
+ enum: availableTestIds,
35
+ },
36
+ },
37
+ groupSummary: {
38
+ type: "string",
39
+ description: "Summary of the grouped tests",
40
+ },
41
+ },
42
+ required: ["testIds", "groupSummary"],
43
+ additionalProperties: false,
44
+ },
45
+ },
46
+ },
47
+ required: ["groups"],
48
+ additionalProperties: false,
49
+ },
50
+ },
51
+ });
52
+ const generateGroupedSummary = async ({ testRunId, tests, trace, }) => {
53
+ let output = null;
54
+ const availableTestIds = tests.map((t) => t.testId);
55
+ try {
56
+ const messages = (0, llm_1.compilePrompt)(promptTemplate_0, {
57
+ testList: JSON.stringify(tests),
58
+ });
59
+ trace?.event({
60
+ input: {
61
+ testList: JSON.stringify(tests),
62
+ },
63
+ output: { messages },
64
+ });
65
+ const llm = new llm_1.LLM({
66
+ trace,
67
+ provider: "openai",
68
+ defaultModel: "gpt-4o",
69
+ });
70
+ const llmResponse = await llm.createChatCompletion({
71
+ messages,
72
+ trace,
73
+ modelParameters: {
74
+ temperature: 0.2,
75
+ },
76
+ responseFormat: getResponseFormat(availableTestIds),
77
+ });
78
+ output = groupedSummarySchema.parse(JSON.parse(llmResponse?.content).groups);
79
+ }
80
+ catch (e) {
81
+ console.error(`Error grouping tests, returning all tests without groups for testRunId - "${testRunId}"`, e?.message);
82
+ output = tests.map((test) => {
83
+ return {
84
+ testIds: [test.testId],
85
+ groupSummary: test.errorSummary,
86
+ };
87
+ });
88
+ }
89
+ return output;
90
+ };
91
+ exports.generateGroupedSummary = generateGroupedSummary;
@@ -0,0 +1,16 @@
1
+ import { TraceClient } from "@empiricalrun/llm";
2
+ import { TestGroup } from "@empiricalrun/shared-types/failure-workflow";
3
+ type ArgsT = {
4
+ testRunId: number;
5
+ test: TestGroup;
6
+ errorSummary: string;
7
+ videoDiffSummary: string;
8
+ networkFailures: {
9
+ endpoint: string;
10
+ status: number;
11
+ }[];
12
+ trace?: TraceClient;
13
+ };
14
+ export declare const mergeSummary: ({ testRunId, test, errorSummary: errorStackSummary, networkFailures, videoDiffSummary, trace, }: ArgsT) => Promise<string>;
15
+ export {};
16
+ //# sourceMappingURL=merge-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-summary.d.ts","sourceRoot":"","sources":["../../src/generate-summary/merge-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAC;AAGxE,KAAK,KAAK,GAAG;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxD,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AACF,eAAO,MAAM,YAAY,GAAU,iGAOhC,KAAK,KAAG,OAAO,CAAC,MAAM,CA2CxB,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.mergeSummary = void 0;
7
+ const llm_1 = require("@empiricalrun/llm");
8
+ const merge_summary_handlebars_1 = __importDefault(require("@/prompts/merge-summary.handlebars"));
9
+ const mergeSummary = async ({ testRunId, test, errorSummary: errorStackSummary, networkFailures, videoDiffSummary, trace, }) => {
10
+ let output = "";
11
+ const payload = {
12
+ test: JSON.stringify({
13
+ errorSummary: videoDiffSummary || errorStackSummary,
14
+ networkFailures,
15
+ }),
16
+ };
17
+ try {
18
+ const messages = (0, llm_1.compilePrompt)(promptTemplate_0, payload);
19
+ trace?.event({
20
+ input: {
21
+ payload,
22
+ },
23
+ output: { messages },
24
+ });
25
+ const llm = new llm_1.LLM({
26
+ trace,
27
+ // defaultModel: "gpt-4o-2024-08-06",
28
+ providerApiKey: process.env.ANTHROPIC_API_KEY,
29
+ provider: "anthropic",
30
+ defaultModel: "claude-3-5-sonnet-latest",
31
+ });
32
+ const llmResponse = await llm.createChatCompletion({
33
+ messages: messages,
34
+ trace,
35
+ modelParameters: {
36
+ temperature: 0.2,
37
+ },
38
+ });
39
+ output = llmResponse?.content ?? "";
40
+ }
41
+ catch (e) {
42
+ throw new Error(`Failed to merge network+video+error stack summary for test - "${test.title}", testRunId - "${testRunId}"`, e.message);
43
+ }
44
+ return output;
45
+ };
46
+ exports.mergeSummary = mergeSummary;
@@ -0,0 +1,9 @@
1
+ import { TestGroup } from "@empiricalrun/shared-types/failure-workflow";
2
+ export declare const pickVideosForComparison: ({ testRunId, test, }: {
3
+ testRunId: number;
4
+ test: TestGroup;
5
+ }) => Promise<{
6
+ failure: string;
7
+ success: string;
8
+ }>;
9
+ //# sourceMappingURL=pick-videos-for-comparison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pick-videos-for-comparison.d.ts","sourceRoot":"","sources":["../../src/generate-summary/pick-videos-for-comparison.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAC;AAExE,eAAO,MAAM,uBAAuB,GAAU,sBAG3C;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;CACjB,KAAG,OAAO,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAiEA,CAAC"}