@empiricalrun/playwright-utils 0.28.8 → 0.30.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/CHANGELOG.md +21 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +9 -10
- package/dist/reporter/empirical-reporter.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.js +37 -41
- package/dist/reporter/uploader.d.ts +1 -12
- package/dist/reporter/uploader.d.ts.map +1 -1
- package/dist/reporter/uploader.js +0 -42
- package/dist/reporter/util.d.ts +2 -3
- package/dist/reporter/util.d.ts.map +1 -1
- package/dist/test/index.d.ts +2 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +58 -13
- package/dist/test/video-labels.d.ts +5 -0
- package/dist/test/video-labels.d.ts.map +1 -0
- package/dist/test/video-labels.js +25 -0
- package/docs/video-labels.md +42 -0
- package/package.json +4 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/reporter/queue.d.ts +0 -8
- package/dist/reporter/queue.d.ts.map +0 -1
- package/dist/reporter/queue.js +0 -88
- package/dist/reporter/types.d.ts +0 -3
- package/dist/reporter/types.d.ts.map +0 -1
- package/dist/reporter/types.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @empiricalrun/playwright-utils
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7cbf29a: feat: add setVideoLabel for multi-page scenarios to have labels
|
|
8
|
+
|
|
9
|
+
## 0.29.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- b01be05: feat: move task queue to r2-uploader package
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 6920cda: feat: send all attachments from pw-utils to dash
|
|
18
|
+
- Updated dependencies [b01be05]
|
|
19
|
+
- Updated dependencies [e65313e]
|
|
20
|
+
- @empiricalrun/r2-uploader@0.4.0
|
|
21
|
+
- @empiricalrun/test-gen@0.78.0
|
|
22
|
+
- @empiricalrun/llm@0.24.0
|
|
23
|
+
|
|
3
24
|
## 0.28.8
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAwCD,eAAO,MAAM,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAwCD,eAAO,MAAM,UAAU,EAAE,oBAyBxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAarB,CAAC"}
|
package/dist/config/index.js
CHANGED
|
@@ -77,20 +77,19 @@ exports.baseConfig = {
|
|
|
77
77
|
workers: undefined,
|
|
78
78
|
// maxFailures counts retries as individual failures, so this is equivalent to 20 failed tests
|
|
79
79
|
maxFailures: process.env.CI ? 30 : undefined,
|
|
80
|
+
timeout: 15 * 60 * 1_000,
|
|
81
|
+
expect: {
|
|
82
|
+
timeout: 15_000,
|
|
83
|
+
},
|
|
80
84
|
use: {
|
|
81
|
-
screenshot: "on",
|
|
82
|
-
trace: "on",
|
|
83
|
-
video: {
|
|
84
|
-
mode: "on",
|
|
85
|
-
size: { width: 1280, height: 720 },
|
|
86
|
-
},
|
|
87
85
|
actionTimeout: 15_000,
|
|
88
86
|
navigationTimeout: 30_000,
|
|
87
|
+
screenshot: "on",
|
|
88
|
+
trace: "on",
|
|
89
|
+
// Video recording is handled by the context fixture override
|
|
90
|
+
// so that we can use video labels
|
|
91
|
+
video: "off",
|
|
89
92
|
},
|
|
90
|
-
expect: {
|
|
91
|
-
timeout: 15_000,
|
|
92
|
-
},
|
|
93
|
-
timeout: 15 * 60 * 1_000,
|
|
94
93
|
};
|
|
95
94
|
exports.devices = Object.entries(deviceDescriptorsSource_json_1.default).reduce((acc, [name, device]) => ({
|
|
96
95
|
...acc,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAanC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAAkB;IAC9C,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,UAAU,CACwE;IAE1F,OAAO,CAAC,YAAY;;IAyBpB,OAAO,CAAC,6BAA6B,CA4BnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAsEtC,KAAK,CAAC,MAAM,EAAE,UAAU;CA+G/B;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -3,11 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
6
|
+
const r2_uploader_1 = require("@empiricalrun/r2-uploader");
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const logger_1 = require("../logger");
|
|
10
|
-
const queue_1 = require("./queue");
|
|
11
10
|
const uploader_1 = require("./uploader");
|
|
12
11
|
const util_1 = require("./util");
|
|
13
12
|
class EmpiricalReporter {
|
|
@@ -37,8 +36,10 @@ class EmpiricalReporter {
|
|
|
37
36
|
this._willUploadArtifacts = this.checkR2Creds();
|
|
38
37
|
}
|
|
39
38
|
enqueTestAttachmentUploadTask = async (attachment) => {
|
|
40
|
-
if (!attachment.path)
|
|
39
|
+
if (!attachment.path) {
|
|
40
|
+
logger_1.logger.warn(`[Empirical Reporter] Skipping attachment without path: ${attachment.name}`);
|
|
41
41
|
return;
|
|
42
|
+
}
|
|
42
43
|
const exists = await (0, util_1.checkFileExistsAsync)(attachment.path);
|
|
43
44
|
if (exists) {
|
|
44
45
|
logger_1.logger.debug(`[Empirical Reporter] Processing attachment: File exists`);
|
|
@@ -54,7 +55,7 @@ class EmpiricalReporter {
|
|
|
54
55
|
uploadBucket: this._uploadBucket,
|
|
55
56
|
baseUrl: this._baseUrl,
|
|
56
57
|
});
|
|
57
|
-
return (0,
|
|
58
|
+
return (0, r2_uploader_1.sendTaskToQueue)(uploadTask);
|
|
58
59
|
};
|
|
59
60
|
onTestEnd(test, result) {
|
|
60
61
|
if (!this._willUploadArtifacts) {
|
|
@@ -65,55 +66,50 @@ class EmpiricalReporter {
|
|
|
65
66
|
return;
|
|
66
67
|
}
|
|
67
68
|
try {
|
|
68
|
-
const attachmentPromises = result.attachments.map((attachment) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const attachmentPromises = result.attachments.map(async (attachment) => {
|
|
70
|
+
const fileMap = await this.enqueTestAttachmentUploadTask(attachment);
|
|
71
|
+
if (!fileMap) {
|
|
72
|
+
logger_1.logger.error(`No upload result for ${attachment}`);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
// Since we are uploading a single attachmnet, the fileMap will have one entry
|
|
76
|
+
const url = Object.values(fileMap)[0];
|
|
77
|
+
if (!url) {
|
|
78
|
+
logger_1.logger.error(`No url in file map for ${attachment}`);
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (!attachment.path) {
|
|
82
|
+
// This should never happen because we only upload if attachment.path is there
|
|
83
|
+
logger_1.logger.error(`No path in file map for ${attachment}`);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
name: attachment.name,
|
|
88
|
+
contentType: attachment.contentType,
|
|
89
|
+
path: attachment.path,
|
|
90
|
+
url,
|
|
91
|
+
};
|
|
73
92
|
});
|
|
74
93
|
const testCaseRunEventTask = async () => {
|
|
75
94
|
return Promise.all(attachmentPromises)
|
|
76
95
|
.then((uploadedAttachments) => {
|
|
77
96
|
logger_1.logger.debug(`[Empirical Reporter] Attachments for test ${test.title} are uploaded:`, uploadedAttachments);
|
|
78
97
|
const { suites, projectName } = (0, util_1.suitesAndProjectForTest)(test);
|
|
79
|
-
|
|
98
|
+
const params = {
|
|
80
99
|
test,
|
|
81
|
-
result,
|
|
82
|
-
assetsURL: {
|
|
83
|
-
trace: "",
|
|
84
|
-
videos: [],
|
|
85
|
-
errorContext: "",
|
|
86
|
-
},
|
|
87
100
|
suites,
|
|
101
|
+
result,
|
|
88
102
|
projectName,
|
|
89
|
-
|
|
103
|
+
attachments: uploadedAttachments.filter((a) => a !== undefined),
|
|
104
|
+
runId: process.env.TEST_RUN_GITHUB_ACTION_ID,
|
|
90
105
|
};
|
|
91
|
-
uploadedAttachments.forEach((item) => {
|
|
92
|
-
if (item.fileMap && item.originalAttachment) {
|
|
93
|
-
const { fileMap, originalAttachment } = item;
|
|
94
|
-
Object.entries(fileMap).forEach(([key, value]) => {
|
|
95
|
-
if (originalAttachment.contentType.startsWith("video/")) {
|
|
96
|
-
params.assetsURL.videos.push({
|
|
97
|
-
name: `test-${test.id}-slug-${(0, crypto_1.randomUUID)()}`,
|
|
98
|
-
url: value,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
if (originalAttachment.name.includes("trace")) {
|
|
102
|
-
params.assetsURL.trace = value;
|
|
103
|
-
}
|
|
104
|
-
if (originalAttachment.name.includes("error-context")) {
|
|
105
|
-
params.assetsURL.errorContext = value;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
106
|
return (0, util_1.sendTestCaseUpdateToDashboard)(params);
|
|
111
107
|
})
|
|
112
108
|
.catch((error) => {
|
|
113
109
|
logger_1.logger.error(`[Empirical Reporter] Error sending test case event for: ${test.title}:`, error);
|
|
114
110
|
});
|
|
115
111
|
};
|
|
116
|
-
void (0,
|
|
112
|
+
void (0, r2_uploader_1.sendTaskToQueue)(testCaseRunEventTask);
|
|
117
113
|
return;
|
|
118
114
|
}
|
|
119
115
|
catch (error) {
|
|
@@ -146,7 +142,7 @@ class EmpiricalReporter {
|
|
|
146
142
|
uploadBucket: this._uploadBucket,
|
|
147
143
|
baseUrl: this._baseUrl,
|
|
148
144
|
});
|
|
149
|
-
void (0,
|
|
145
|
+
void (0, r2_uploader_1.sendTaskToQueue)(uploadHtmlTask);
|
|
150
146
|
}
|
|
151
147
|
else {
|
|
152
148
|
logger_1.logger.error(`[Empirical Reporter] playwright-report/index.html does not exist at: ${htmlFilePath}`);
|
|
@@ -162,7 +158,7 @@ class EmpiricalReporter {
|
|
|
162
158
|
uploadBucket: this._uploadBucket,
|
|
163
159
|
baseUrl: this._baseUrl,
|
|
164
160
|
});
|
|
165
|
-
void (0,
|
|
161
|
+
void (0, r2_uploader_1.sendTaskToQueue)(uploadJsonTask);
|
|
166
162
|
}
|
|
167
163
|
else {
|
|
168
164
|
logger_1.logger.error(`[Empirical Reporter] summary.json does not exist at: ${jsonFilePath}`);
|
|
@@ -177,13 +173,13 @@ class EmpiricalReporter {
|
|
|
177
173
|
uploadBucket: this._uploadBucket,
|
|
178
174
|
baseUrl: this._baseUrl,
|
|
179
175
|
});
|
|
180
|
-
void (0,
|
|
176
|
+
void (0, r2_uploader_1.sendTaskToQueue)(uploadTraceTask);
|
|
181
177
|
}
|
|
182
178
|
else {
|
|
183
179
|
logger_1.logger.error(`[Empirical Reporter] playwright-report/trace does not exist at: ${traceFilePath}`);
|
|
184
180
|
return;
|
|
185
181
|
}
|
|
186
|
-
await (0,
|
|
182
|
+
await (0, r2_uploader_1.waitForTaskQueueToFinish)();
|
|
187
183
|
logger_1.logger.debug("[Empirical Reporter] All uploads finished");
|
|
188
184
|
const endTime = new Date().getTime();
|
|
189
185
|
logger_1.logger.debug("[Empirical Reporter] Finished final report upload at: ", new Intl.DateTimeFormat("en-US", {
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AsyncTask } from "./queue";
|
|
3
|
-
export declare function uploadFiles({ fileList, outputFolder, relativePath, }: {
|
|
4
|
-
outputFolder: string;
|
|
5
|
-
fileList?: string[];
|
|
6
|
-
relativePath: string;
|
|
7
|
-
}): Promise<FileMap | void>;
|
|
8
|
-
export declare function createUploadTask({ fileList, outputFolder, relativePath, }: {
|
|
9
|
-
outputFolder: string;
|
|
10
|
-
fileList?: string[];
|
|
11
|
-
relativePath?: string;
|
|
12
|
-
}): AsyncTask;
|
|
1
|
+
import { AsyncTask } from "@empiricalrun/r2-uploader";
|
|
13
2
|
/**
|
|
14
3
|
* Creates an async task for uploading files to R2 storage.
|
|
15
4
|
* This is an improved version of createUploadTask that:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA4B,MAAM,2BAA2B,CAAC;AAKhF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,SAAS,CA8BZ"}
|
|
@@ -3,52 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.uploadFiles = uploadFiles;
|
|
7
|
-
exports.createUploadTask = createUploadTask;
|
|
8
6
|
exports.createUploadTaskV2 = createUploadTaskV2;
|
|
9
7
|
const r2_uploader_1 = require("@empiricalrun/r2-uploader");
|
|
10
8
|
const path_1 = __importDefault(require("path"));
|
|
11
9
|
const logger_1 = require("../logger");
|
|
12
|
-
// TODO: Remove this once we have migrated to 1.53.X for all repos
|
|
13
|
-
const R2_BUCKET_NAME = "test-report";
|
|
14
|
-
const R2_BASE_URL = "https://reports.empirical.run";
|
|
15
|
-
// TODO: Remove this once we have migrated to 1.53.X for all repos
|
|
16
|
-
async function uploadFiles({ fileList, outputFolder, relativePath, }) {
|
|
17
|
-
if (!process.env.PROJECT_NAME || !process.env.TEST_RUN_GITHUB_ACTION_ID) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const urls = {};
|
|
21
|
-
const sourceDir = path_1.default.join(outputFolder, relativePath);
|
|
22
|
-
const destinationDir = path_1.default.join(process.env.PROJECT_NAME, process.env.TEST_RUN_GITHUB_ACTION_ID, relativePath);
|
|
23
|
-
const uploadedfiles = await (0, r2_uploader_1.uploadDirectory)({
|
|
24
|
-
sourceDir,
|
|
25
|
-
fileList,
|
|
26
|
-
destinationDir,
|
|
27
|
-
uploadBucket: R2_BUCKET_NAME,
|
|
28
|
-
});
|
|
29
|
-
Object.entries(uploadedfiles).forEach(([filePath]) => {
|
|
30
|
-
const fileName = filePath.split("/").pop() || "";
|
|
31
|
-
const fileUrl = new URL(path_1.default.join(destinationDir, fileName), R2_BASE_URL);
|
|
32
|
-
urls[fileName] = fileUrl.toString();
|
|
33
|
-
});
|
|
34
|
-
logger_1.logger.debug("Finished uploading files:", urls);
|
|
35
|
-
return urls;
|
|
36
|
-
}
|
|
37
|
-
// TODO: Remove this once we have migrated to 1.53.X for all repos
|
|
38
|
-
function createUploadTask({ fileList, outputFolder, relativePath = "", }) {
|
|
39
|
-
return async () => {
|
|
40
|
-
return uploadFiles({
|
|
41
|
-
fileList,
|
|
42
|
-
outputFolder,
|
|
43
|
-
relativePath,
|
|
44
|
-
})
|
|
45
|
-
.then((fileMap) => {
|
|
46
|
-
logger_1.logger.debug("Finished uploading files", fileMap);
|
|
47
|
-
return fileMap;
|
|
48
|
-
})
|
|
49
|
-
.catch((e) => logger_1.logger.error("Error uploading files", e));
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
10
|
/**
|
|
53
11
|
* Creates an async task for uploading files to R2 storage.
|
|
54
12
|
* This is an improved version of createUploadTask that:
|
package/dist/reporter/util.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { GenericPlaywrightAttachment } from "@empiricalrun/shared-types";
|
|
1
|
+
import { GenericPlaywrightAttachment, TestCaseRunEndEventV2 } from "@empiricalrun/shared-types";
|
|
2
2
|
import { JSONReportSpec, JSONReport as PlaywrightJSONReport, TestCase } from "@playwright/test/reporter";
|
|
3
|
-
import { TestCaseRunEndEvent } from "./types";
|
|
4
3
|
export declare function getPackageJsonPath(folderPath: string): string;
|
|
5
4
|
export declare function resolveReporterOutputPath(defaultValue: string, configDir: string, configValue: string | undefined): string;
|
|
6
5
|
export declare function normalizeAndSaveAttachment(outputPath: string, name: string, options?: {
|
|
@@ -17,7 +16,7 @@ export declare function suitesAndProjectForTest(test: TestCase): {
|
|
|
17
16
|
suites: string[];
|
|
18
17
|
projectName: string;
|
|
19
18
|
};
|
|
20
|
-
export declare function sendTestCaseUpdateToDashboard(params:
|
|
19
|
+
export declare function sendTestCaseUpdateToDashboard(params: TestCaseRunEndEventV2): Promise<void>;
|
|
21
20
|
export declare function safelySerialiseJSON(obj: any, keyFilter?: (key: string) => boolean): any;
|
|
22
21
|
export declare function updateHtmlZipFileAttachmentPaths(jsonFilePath: string, htmlFilePath: string, repoPath: string): Promise<void>;
|
|
23
22
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,cAAc,EAGd,UAAU,IAAI,oBAAoB,EAElC,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAoBnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAMhC;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5E,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAyCD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;;;EAwBrD;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACnC,GAAG,CAIL;AAED,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,iBAuFjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CACjD,QA+BF;AAGD,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,oBAAoB,EACjC,MAAM,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,QAavC;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,GAAG,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CAAC,CA4C/D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CAoClB"}
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BrowserContext, BrowserContextOptions, expect, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestType } from "@playwright/test";
|
|
2
2
|
import { injectLocatorHighlightScripts } from "./scripts";
|
|
3
3
|
import { HighlighterOpts } from "./types";
|
|
4
|
+
import { setVideoLabel } from "./video-labels";
|
|
4
5
|
declare global {
|
|
5
6
|
namespace PlaywrightTest {
|
|
6
7
|
interface Matchers<R> {
|
|
@@ -18,5 +19,5 @@ type TestOptions = {
|
|
|
18
19
|
saveVideos: void;
|
|
19
20
|
};
|
|
20
21
|
declare const baseTestFixture: (testFn: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>, options?: HighlighterOpts) => TestType<PlaywrightTestArgs & PlaywrightTestOptions & TestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
21
|
-
export { baseTestFixture, extendExpect, injectLocatorHighlightScripts };
|
|
22
|
+
export { baseTestFixture, extendExpect, injectLocatorHighlightScripts, setVideoLabel, };
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/test/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAA8B,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,cAAc,CAAC;QACvB,UAAU,QAAQ,CAAC,CAAC;YAClB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;SAC9C;KACF;CACF;AAED,QAAA,MAAM,YAAY,GAAa,gBAAgB,OAAO,MAAM,0CAG3D,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,yBAAyB,EAAE,CACzB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAYF,QAAA,MAAM,eAAe,GACnB,QAAQ,QAAQ,CACd,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CAC/C,EACD,UAAS,eAAgC,uHAmG1C,CAAC;AAEF,OAAO,EACL,eAAe,EACf,YAAY,EACZ,6BAA6B,EAC7B,aAAa,GACd,CAAC"}
|
package/dist/test/index.js
CHANGED
|
@@ -3,19 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.injectLocatorHighlightScripts = exports.extendExpect = exports.baseTestFixture = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
6
|
+
exports.setVideoLabel = exports.injectLocatorHighlightScripts = exports.extendExpect = exports.baseTestFixture = void 0;
|
|
8
7
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const rimraf_1 = require("rimraf");
|
|
10
8
|
const expect_1 = require("./expect");
|
|
11
9
|
const scripts_1 = require("./scripts");
|
|
12
10
|
Object.defineProperty(exports, "injectLocatorHighlightScripts", { enumerable: true, get: function () { return scripts_1.injectLocatorHighlightScripts; } });
|
|
11
|
+
const video_labels_1 = require("./video-labels");
|
|
12
|
+
Object.defineProperty(exports, "setVideoLabel", { enumerable: true, get: function () { return video_labels_1.setVideoLabel; } });
|
|
13
13
|
const extendExpect = function (expectInstance) {
|
|
14
14
|
expectInstance.extend({ toLookRight: expect_1.toLookRight });
|
|
15
15
|
return expectInstance;
|
|
16
16
|
};
|
|
17
17
|
exports.extendExpect = extendExpect;
|
|
18
|
-
|
|
18
|
+
// Track pages per test to save videos with labels
|
|
19
|
+
const testPages = new Map();
|
|
19
20
|
const defaultOptions = {
|
|
20
21
|
mousePointerHighlighter: true,
|
|
21
22
|
locatorHighlighter: true,
|
|
@@ -24,6 +25,22 @@ const defaultOptions = {
|
|
|
24
25
|
};
|
|
25
26
|
const baseTestFixture = function (testFn, options = defaultOptions) {
|
|
26
27
|
const extendedTestFn = testFn.extend({
|
|
28
|
+
context: async ({ browser }, use, testInfo) => {
|
|
29
|
+
const context = await browser.newContext({
|
|
30
|
+
recordVideo: {
|
|
31
|
+
dir: testInfo.outputDir,
|
|
32
|
+
size: { width: 1280, height: 720 },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const pages = [];
|
|
36
|
+
context.on("page", (page) => pages.push(page));
|
|
37
|
+
await use(context);
|
|
38
|
+
if (!testPages.has(testInfo.testId)) {
|
|
39
|
+
testPages.set(testInfo.testId, []);
|
|
40
|
+
}
|
|
41
|
+
testPages.get(testInfo.testId).push(...pages);
|
|
42
|
+
await context.close();
|
|
43
|
+
},
|
|
27
44
|
page: async ({ page }, use) => {
|
|
28
45
|
(0, scripts_1.injectLocatorHighlightScripts)(page, extendedTestFn, {
|
|
29
46
|
...defaultOptions,
|
|
@@ -37,32 +54,60 @@ const baseTestFixture = function (testFn, options = defaultOptions) {
|
|
|
37
54
|
const pageContext = await browser.newContext({
|
|
38
55
|
...contextOptions,
|
|
39
56
|
recordVideo: {
|
|
40
|
-
dir:
|
|
57
|
+
dir: testInfo.outputDir,
|
|
58
|
+
size: { width: 1280, height: 720 },
|
|
41
59
|
},
|
|
42
60
|
});
|
|
43
|
-
|
|
61
|
+
const pages = [];
|
|
62
|
+
pageContext.on("page", (page) => pages.push(page));
|
|
44
63
|
const customPage = await pageContext.newPage();
|
|
64
|
+
contexts.push({ context: pageContext, pages });
|
|
45
65
|
(0, scripts_1.injectLocatorHighlightScripts)(customPage, extendedTestFn, options);
|
|
46
66
|
return { context: pageContext, page: customPage };
|
|
47
67
|
}
|
|
48
68
|
await use(createContext);
|
|
49
|
-
|
|
69
|
+
// Store pages for video renaming
|
|
70
|
+
if (!testPages.has(testInfo.testId)) {
|
|
71
|
+
testPages.set(testInfo.testId, []);
|
|
72
|
+
}
|
|
73
|
+
for (const { pages } of contexts) {
|
|
74
|
+
testPages.get(testInfo.testId).push(...pages);
|
|
75
|
+
}
|
|
76
|
+
for (const { context } of contexts) {
|
|
50
77
|
await context.close();
|
|
51
78
|
}
|
|
52
79
|
},
|
|
53
80
|
saveVideos: [
|
|
54
81
|
async ({}, use, testInfo) => {
|
|
55
82
|
await use();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
const pages = testPages.get(testInfo.testId) || [];
|
|
84
|
+
const usedLabels = new Set();
|
|
85
|
+
// Save videos with custom labels using saveAs
|
|
86
|
+
for (let i = 0; i < pages.length; i++) {
|
|
87
|
+
const page = pages[i];
|
|
88
|
+
if (!page)
|
|
89
|
+
continue;
|
|
90
|
+
const video = page.video();
|
|
91
|
+
if (!video)
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
const srcPath = await video.path();
|
|
95
|
+
const ext = path_1.default.extname(srcPath) || ".webm";
|
|
96
|
+
const rawLabel = (0, video_labels_1.getVideoLabel)(page) || `video-${i}`;
|
|
97
|
+
const label = (0, video_labels_1.dedupeLabel)(rawLabel, usedLabels);
|
|
98
|
+
const outPath = testInfo.outputPath(`${label}${ext}`);
|
|
99
|
+
await video.saveAs(outPath);
|
|
100
|
+
await testInfo.attach(`video: ${label}`, {
|
|
101
|
+
path: outPath,
|
|
61
102
|
contentType: "video/webm",
|
|
62
103
|
});
|
|
104
|
+
await video.delete();
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
// Skip if video not available
|
|
63
108
|
}
|
|
64
|
-
(0, rimraf_1.rimrafSync)(pathToTestVideos);
|
|
65
109
|
}
|
|
110
|
+
testPages.delete(testInfo.testId);
|
|
66
111
|
},
|
|
67
112
|
{ auto: true },
|
|
68
113
|
],
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from "@playwright/test";
|
|
2
|
+
export declare function setVideoLabel(page: Page, label: string): void;
|
|
3
|
+
export declare function getVideoLabel(page: Page): string | undefined;
|
|
4
|
+
export declare function dedupeLabel(baseLabel: string, usedLabels: Set<string>): string;
|
|
5
|
+
//# sourceMappingURL=video-labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-labels.d.ts","sourceRoot":"","sources":["../../src/test/video-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAGtD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CAQR"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setVideoLabel = setVideoLabel;
|
|
4
|
+
exports.getVideoLabel = getVideoLabel;
|
|
5
|
+
exports.dedupeLabel = dedupeLabel;
|
|
6
|
+
const videoLabels = new WeakMap();
|
|
7
|
+
function sanitizeLabel(label) {
|
|
8
|
+
return label.replace(/[^a-z0-9._-]/gi, "_").slice(0, 80) || "video";
|
|
9
|
+
}
|
|
10
|
+
function setVideoLabel(page, label) {
|
|
11
|
+
// Sanitize immediately when setting
|
|
12
|
+
videoLabels.set(page, sanitizeLabel(label));
|
|
13
|
+
}
|
|
14
|
+
function getVideoLabel(page) {
|
|
15
|
+
return videoLabels.get(page);
|
|
16
|
+
}
|
|
17
|
+
function dedupeLabel(baseLabel, usedLabels) {
|
|
18
|
+
let label = baseLabel;
|
|
19
|
+
let counter = 1;
|
|
20
|
+
while (usedLabels.has(label)) {
|
|
21
|
+
label = `${baseLabel}-${counter++}`;
|
|
22
|
+
}
|
|
23
|
+
usedLabels.add(label);
|
|
24
|
+
return label;
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Video Labels
|
|
2
|
+
|
|
3
|
+
Pages generate video recordings after test execution, with 1 page generating 1 video file (webm).
|
|
4
|
+
|
|
5
|
+
If your test case relies on multiple pages (e.g. for multi-user or multi-app flows), it can get difficult to
|
|
6
|
+
know which page does "video-1.webm" belong to.
|
|
7
|
+
|
|
8
|
+
To solve this, you should set video labels for pages. This will enable you to identify videos faster.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { setVideoLabel } from 'playwright-utils/test';
|
|
14
|
+
|
|
15
|
+
test('my test', async ({ page }) => {
|
|
16
|
+
setVideoLabel(page, 'checkout-flow');
|
|
17
|
+
// Video will be saved as 'checkout-flow.webm'
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Multiple Contexts
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
test('multi-user scenario', async ({ page, customContextPageProvider }) => {
|
|
25
|
+
setVideoLabel(page, 'host-page');
|
|
26
|
+
|
|
27
|
+
const { page: guestPage } = await customContextPageProvider({ storageState: undefined });
|
|
28
|
+
setVideoLabel(guestPage, 'guest-page');
|
|
29
|
+
// Videos saved as 'guest-page.webm' and 'host-page.webm'
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Default Behavior
|
|
34
|
+
|
|
35
|
+
If no label is set:
|
|
36
|
+
- Default `page` fixture: `video.webm`
|
|
37
|
+
- `customContextPageProvider`: `video-0.webm`, `video-1.webm`, etc.
|
|
38
|
+
|
|
39
|
+
## Note
|
|
40
|
+
|
|
41
|
+
If setVideoLabel is called twice for the same page:
|
|
42
|
+
- The last label will be set
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"mailosaur": "^8.6.1",
|
|
44
44
|
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
|
45
45
|
"rimraf": "^6.0.1",
|
|
46
|
-
"@empiricalrun/llm": "^0.
|
|
47
|
-
"@empiricalrun/r2-uploader": "^0.
|
|
48
|
-
"@empiricalrun/test-gen": "^0.
|
|
46
|
+
"@empiricalrun/llm": "^0.24.0",
|
|
47
|
+
"@empiricalrun/r2-uploader": "^0.4.0",
|
|
48
|
+
"@empiricalrun/test-gen": "^0.78.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"dev": "tsc --build --watch",
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/email.ts","./src/index.ts","./src/logger.ts","./src/playwright-extensions.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/captcha/vision.ts","./src/config/index.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/empirical-reporter.ts","./src/reporter/
|
|
1
|
+
{"root":["./src/email.ts","./src/index.ts","./src/logger.ts","./src/playwright-extensions.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/captcha/vision.ts","./src/config/index.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/empirical-reporter.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/expect.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|
package/dist/reporter/queue.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { FileMap } from "@empiricalrun/r2-uploader";
|
|
2
|
-
export type AsyncTask = () => Promise<FileMap | void>;
|
|
3
|
-
type AsyncTaskReturnType = FileMap | void;
|
|
4
|
-
type AsyncTaskResult = Promise<AsyncTaskReturnType>;
|
|
5
|
-
export declare function sendTaskToQueue(task: AsyncTask): AsyncTaskResult;
|
|
6
|
-
export declare function waitForTaskQueueToFinish(): Promise<void>;
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/reporter/queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAIpD,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AACtD,KAAK,mBAAmB,GAAG,OAAO,GAAG,IAAI,CAAC;AAC1C,KAAK,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAoGpD,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAQhE;AAED,wBAAgB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOxD"}
|
package/dist/reporter/queue.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sendTaskToQueue = sendTaskToQueue;
|
|
4
|
-
exports.waitForTaskQueueToFinish = waitForTaskQueueToFinish;
|
|
5
|
-
const logger_1 = require("../logger");
|
|
6
|
-
class TaskQueue {
|
|
7
|
-
waitingQueue = [];
|
|
8
|
-
executingTasks = [];
|
|
9
|
-
maxConcurrent;
|
|
10
|
-
constructor() {
|
|
11
|
-
this.maxConcurrent = process.env.UPLOAD_MAX_QUEUE_SIZE
|
|
12
|
-
? parseInt(process.env.UPLOAD_MAX_QUEUE_SIZE)
|
|
13
|
-
: 2;
|
|
14
|
-
}
|
|
15
|
-
async processNextTask() {
|
|
16
|
-
logger_1.logger.debug("[queue] processing next task in queue, with queue lengths:", taskQueue.queueLength, taskQueue.executingTasksCount);
|
|
17
|
-
if (this.waitingQueue.length === 0 ||
|
|
18
|
-
this.executingTasks.length >= this.maxConcurrent) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const queuedTask = this.waitingQueue.shift();
|
|
22
|
-
if (!queuedTask)
|
|
23
|
-
return;
|
|
24
|
-
const { task, resolve, reject } = queuedTask;
|
|
25
|
-
// Create the promise first
|
|
26
|
-
let executingPromise;
|
|
27
|
-
const executeTask = async () => {
|
|
28
|
-
try {
|
|
29
|
-
const result = await task();
|
|
30
|
-
resolve(result);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger_1.logger.error("Error in processing task", error);
|
|
34
|
-
reject(error);
|
|
35
|
-
}
|
|
36
|
-
finally {
|
|
37
|
-
// Remove this task from executing tasks
|
|
38
|
-
const index = this.executingTasks.indexOf(executingPromise);
|
|
39
|
-
if (index > -1) {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
41
|
-
this.executingTasks.splice(index, 1);
|
|
42
|
-
}
|
|
43
|
-
// Try to process next task
|
|
44
|
-
await this.processNextTask();
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
// Assign the promise
|
|
48
|
-
executingPromise = executeTask();
|
|
49
|
-
this.executingTasks.push(executingPromise);
|
|
50
|
-
}
|
|
51
|
-
enqueue(task) {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
this.waitingQueue.push({ task, resolve, reject });
|
|
54
|
-
this.processNextTask().catch((error) => {
|
|
55
|
-
logger_1.logger.error("Error processing queue", error);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
async waitForCompletion() {
|
|
60
|
-
if (this.waitingQueue.length === 0 && this.executingTasks.length === 0) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
// First wait for all currently executing tasks
|
|
64
|
-
if (this.executingTasks.length > 0) {
|
|
65
|
-
await Promise.all(this.executingTasks);
|
|
66
|
-
}
|
|
67
|
-
// If there are still tasks in the waiting queue, process them
|
|
68
|
-
if (this.waitingQueue.length > 0) {
|
|
69
|
-
await this.processNextTask();
|
|
70
|
-
}
|
|
71
|
-
await this.waitForCompletion();
|
|
72
|
-
}
|
|
73
|
-
get queueLength() {
|
|
74
|
-
return this.waitingQueue.length;
|
|
75
|
-
}
|
|
76
|
-
get executingTasksCount() {
|
|
77
|
-
return this.executingTasks.length;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const taskQueue = new TaskQueue();
|
|
81
|
-
function sendTaskToQueue(task) {
|
|
82
|
-
logger_1.logger.debug("[queue] sending task to queue, with queue lengths:", taskQueue.queueLength, taskQueue.executingTasksCount, task);
|
|
83
|
-
return taskQueue.enqueue(task);
|
|
84
|
-
}
|
|
85
|
-
function waitForTaskQueueToFinish() {
|
|
86
|
-
logger_1.logger.debug("[queue] waiting for queue to finish, with queue lengths:", taskQueue.queueLength, taskQueue.executingTasksCount);
|
|
87
|
-
return taskQueue.waitForCompletion();
|
|
88
|
-
}
|
package/dist/reporter/types.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/reporter/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAEnG,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC"}
|
package/dist/reporter/types.js
DELETED