@piwitests/reporter 0.4.2

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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -0
  3. package/dist/compression.d.ts +5 -0
  4. package/dist/compression.js +39 -0
  5. package/dist/config-wrapper.d.ts +21 -0
  6. package/dist/config-wrapper.js +64 -0
  7. package/dist/config.d.ts +104 -0
  8. package/dist/config.js +127 -0
  9. package/dist/crash-recovery.d.ts +23 -0
  10. package/dist/crash-recovery.js +105 -0
  11. package/dist/file-handler.d.ts +38 -0
  12. package/dist/file-handler.js +166 -0
  13. package/dist/fixtures.d.ts +25 -0
  14. package/dist/fixtures.js +156 -0
  15. package/dist/global-setup-module.d.ts +2 -0
  16. package/dist/global-setup-module.js +4 -0
  17. package/dist/helpers.d.ts +44 -0
  18. package/dist/helpers.js +288 -0
  19. package/dist/http-client.d.ts +42 -0
  20. package/dist/http-client.js +154 -0
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.js +6 -0
  23. package/dist/logger.d.ts +26 -0
  24. package/dist/logger.js +43 -0
  25. package/dist/metadata-collector.d.ts +32 -0
  26. package/dist/metadata-collector.js +243 -0
  27. package/dist/reporter.d.ts +65 -0
  28. package/dist/reporter.js +341 -0
  29. package/dist/run-submitter.d.ts +66 -0
  30. package/dist/run-submitter.js +184 -0
  31. package/dist/serializer.d.ts +45 -0
  32. package/dist/serializer.js +104 -0
  33. package/dist/skip-classify.d.ts +27 -0
  34. package/dist/skip-classify.js +40 -0
  35. package/dist/step-analyzer.d.ts +97 -0
  36. package/dist/step-analyzer.js +216 -0
  37. package/dist/stream-buffer.d.ts +17 -0
  38. package/dist/stream-buffer.js +102 -0
  39. package/dist/stream-manager.d.ts +74 -0
  40. package/dist/stream-manager.js +338 -0
  41. package/dist/types.d.ts +251 -0
  42. package/dist/types.js +14 -0
  43. package/dist/uploader.d.ts +86 -0
  44. package/dist/uploader.js +191 -0
  45. package/package.json +62 -0
@@ -0,0 +1,191 @@
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.Uploader = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const form_data_1 = __importDefault(require("form-data"));
10
+ const serializer_js_1 = require("./serializer.js");
11
+ /**
12
+ * Handles all upload strategies: plain JSON, multipart (with reports and
13
+ * traces), per-test-case file uploads for streaming runs, and report-only
14
+ * uploads for already-submitted streaming runs.
15
+ */
16
+ class Uploader {
17
+ /**
18
+ * @param httpClient HTTP client for server communication.
19
+ * @param fileHandler File discovery and compression helper.
20
+ * @param logger Prefixed logger.
21
+ */
22
+ constructor(httpClient, fileHandler, logger) {
23
+ this.httpClient = httpClient;
24
+ this.fileHandler = fileHandler;
25
+ this.logger = logger;
26
+ }
27
+ /** Submit test results as a plain JSON payload (no file attachments) */
28
+ async uploadJSON(payload, auth) {
29
+ const response = await this.httpClient.postJSON('/api/test-runs/submit', (0, serializer_js_1.serializeRun)(payload, { includeTestCases: true }), auth);
30
+ this.logger.info(`Successfully uploaded test results`);
31
+ if (response.testRunId)
32
+ this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
33
+ return response;
34
+ }
35
+ /** Submit test results as a multipart form with trace files and compressed report directories */
36
+ async uploadWithFiles(payload, reportOptions, auth) {
37
+ const form = new form_data_1.default();
38
+ form.append('projectName', payload.projectName);
39
+ form.append('testRun', JSON.stringify((0, serializer_js_1.serializeRun)(payload, { includeTestCases: false })));
40
+ form.append('testCases', JSON.stringify(payload.testCases.map((tc) => (0, serializer_js_1.toWireTestCase)(tc))));
41
+ await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
42
+ await this.appendFilesToForm(form, payload.testCases, reportOptions.uploadTraces);
43
+ const response = await this.httpClient.postFormData('/api/test-runs/upload', form, auth);
44
+ this.logger.info(`Successfully uploaded test results with files`);
45
+ if (response.testRunId)
46
+ this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
47
+ if (response.reports) {
48
+ for (const r of response.reports)
49
+ this.logger.info(`${r.label}: ${r.path}`);
50
+ }
51
+ return response;
52
+ }
53
+ /** Upload report files for an already-submitted streaming run */
54
+ async uploadReportsForStreamingRun(projectName, runId, reportOptions, startTime, auth) {
55
+ const form = new form_data_1.default();
56
+ form.append('testRunId', String(runId));
57
+ form.append('projectName', projectName);
58
+ form.append('testRun', JSON.stringify({
59
+ status: 'already-submitted',
60
+ startTime,
61
+ duration: 0,
62
+ totalTests: 0,
63
+ passedTests: 0,
64
+ failedTests: 0,
65
+ skippedTests: 0,
66
+ metadata: {},
67
+ }));
68
+ form.append('testCases', JSON.stringify([]));
69
+ await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
70
+ const response = await this.httpClient.postFormData('/api/test-runs/upload', form, auth);
71
+ this.logger.info(`Successfully uploaded reports for streaming run #${runId}`);
72
+ if (response.reports) {
73
+ for (const r of response.reports)
74
+ this.logger.info(`${r.label}: ${r.path}`);
75
+ }
76
+ }
77
+ /**
78
+ * Upload one test case's trace and attachments for a streaming run.
79
+ * The matching `complete` event must have been flushed to the server first.
80
+ * Returns `false` when the case has no files to upload.
81
+ */
82
+ async uploadCaseFiles(projectName, runId, streamToken, testCase, uploadTraces, auth) {
83
+ const attachments = this.fileHandler.findAllAttachments(testCase);
84
+ let traceInfo = null;
85
+ if (uploadTraces) {
86
+ traceInfo = await this.fileHandler.computeSingleTraceHash(testCase);
87
+ }
88
+ if (!traceInfo && attachments.length === 0)
89
+ return false;
90
+ // Skip the trace body when the server already has this blob
91
+ let missingHashes = null;
92
+ if (traceInfo) {
93
+ missingHashes = await this.fileHandler.checkMissingTraces(this.httpClient, projectName, new Map([[0, traceInfo]]), auth);
94
+ }
95
+ const buildForm = (includeTraceFile) => {
96
+ const form = new form_data_1.default();
97
+ form.append('streamToken', streamToken);
98
+ form.append('testCase', JSON.stringify({
99
+ title: testCase.title,
100
+ location: testCase.location,
101
+ retries: testCase.retries ?? 0,
102
+ suitePath: testCase.suitePath ?? null,
103
+ }));
104
+ if (traceInfo) {
105
+ form.append('trace_hash', traceInfo.hash);
106
+ if (includeTraceFile) {
107
+ form.append('trace', fs_1.default.createReadStream(traceInfo.tracePath), {
108
+ filename: path_1.default.basename(traceInfo.tracePath),
109
+ });
110
+ }
111
+ }
112
+ if (attachments.length > 0) {
113
+ form.append('attach_meta', JSON.stringify(attachments.map((a) => ({ name: a.name, contentType: a.contentType, originalName: a.originalName }))));
114
+ for (const a of attachments) {
115
+ form.append('attach_file', fs_1.default.createReadStream(a.path), { filename: a.originalName });
116
+ }
117
+ }
118
+ return form;
119
+ };
120
+ const includeTraceFile = !traceInfo || !missingHashes || missingHashes.has(traceInfo.hash);
121
+ try {
122
+ await this.httpClient.postFormData(`/api/test-runs/${runId}/case-files`, buildForm(includeTraceFile), auth);
123
+ }
124
+ catch (error) {
125
+ // 422: the server doesn't have the blob after all — resend with the file
126
+ if (traceInfo && !includeTraceFile && error.message?.includes('422')) {
127
+ await this.httpClient.postFormData(`/api/test-runs/${runId}/case-files`, buildForm(true), auth);
128
+ }
129
+ else {
130
+ throw error;
131
+ }
132
+ }
133
+ this.logger.debug(`Uploaded files for "${testCase.title}" (trace: ${traceInfo ? 'yes' : 'no'}, attachments: ${attachments.length})`);
134
+ return true;
135
+ }
136
+ async appendReportsToForm(form, reports, uploadReport) {
137
+ const list = reports ? [...reports] : [];
138
+ if (uploadReport && !list.some((r) => r.type === 'html'))
139
+ list.push({ type: 'html' });
140
+ for (const cfg of list) {
141
+ const defaultDir = this.fileHandler.getDefaultReportDirs()[cfg.type] || cfg.type + '-report';
142
+ const reportDir = cfg.dir
143
+ ? this.fileHandler.findReportDirectory(cfg.dir)
144
+ : cfg.type === 'html'
145
+ ? this.fileHandler.findHTMLReportDirectory()
146
+ : this.fileHandler.findReportDirectory(defaultDir);
147
+ if (!reportDir) {
148
+ this.logger.debug(`No report directory found for type '${cfg.type}'`);
149
+ continue;
150
+ }
151
+ const compressed = await this.fileHandler.compressReportDirectory(reportDir);
152
+ if (compressed) {
153
+ form.append(`report_${cfg.type}`, compressed, { filename: `${cfg.type}-report.gz` });
154
+ if (cfg.label)
155
+ form.append(`report_label_${cfg.type}`, cfg.label);
156
+ }
157
+ }
158
+ }
159
+ async appendFilesToForm(form, testCases, uploadTraces) {
160
+ let attachmentCount = 0;
161
+ for (const [i, tc] of testCases.entries()) {
162
+ const attachments = this.fileHandler.findAllAttachments(tc);
163
+ if (attachments.length === 0)
164
+ continue;
165
+ form.append(`attach_meta_${i}`, JSON.stringify(attachments.map((a) => ({
166
+ name: a.name,
167
+ contentType: a.contentType,
168
+ originalName: a.originalName,
169
+ }))));
170
+ for (const a of attachments) {
171
+ form.append(`attach_file_${i}`, fs_1.default.createReadStream(a.path), { filename: a.originalName });
172
+ attachmentCount++;
173
+ }
174
+ }
175
+ if (attachmentCount > 0)
176
+ this.logger.info(`Uploading ${attachmentCount} non-trace attachments`);
177
+ if (!uploadTraces)
178
+ return;
179
+ let traceCount = 0;
180
+ for (const [i, tc] of testCases.entries()) {
181
+ for (const tp of this.fileHandler.findTraceFiles(tc)) {
182
+ if (fs_1.default.existsSync(tp)) {
183
+ form.append(`trace_${i}`, fs_1.default.createReadStream(tp), { filename: path_1.default.basename(tp) });
184
+ traceCount++;
185
+ }
186
+ }
187
+ }
188
+ this.logger.info(`Found ${traceCount} trace files`);
189
+ }
190
+ }
191
+ exports.Uploader = Uploader;
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@piwitests/reporter",
3
+ "version": "0.4.2",
4
+ "description": "Playwright reporter for sending test results to Piwi Dashboard",
5
+ "url": "https://github.com/PiwiTests/platform",
6
+ "homepage": "https://piwitests.github.io",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/PiwiTests/platform"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/PiwiTests/platform/issues"
13
+ },
14
+ "type": "commonjs",
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.js"
22
+ },
23
+ "./fixtures": {
24
+ "types": "./dist/fixtures.d.ts",
25
+ "import": "./dist/fixtures.js",
26
+ "require": "./dist/fixtures.js"
27
+ }
28
+ },
29
+ "keywords": [
30
+ "playwright",
31
+ "reporter",
32
+ "dashboard",
33
+ "test-results"
34
+ ],
35
+ "author": "piwitests",
36
+ "license": "MIT",
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "provenance": true
40
+ },
41
+ "scripts": {
42
+ "reporter:build": "tsc",
43
+ "reporter:dev": "tsc --watch",
44
+ "reporter:format": "oxfmt --config oxfmt.config.mts --write \"src/\"",
45
+ "reporter:format:check": "oxfmt --config oxfmt.config.mts --check \"src/\"",
46
+ "reporter:lint": "oxlint --config oxlint.config.mts .",
47
+ "reporter:lint:fix": "oxlint --config oxlint.config.mts . --fix",
48
+ "reporter:test": "vitest run",
49
+ "reporter:test:watch": "vitest",
50
+ "test": "npm run reporter:test",
51
+ "prepublishOnly": "npm run reporter:build"
52
+ },
53
+ "files": [
54
+ "dist/"
55
+ ],
56
+ "peerDependencies": {
57
+ "@playwright/test": "^1.40.0"
58
+ },
59
+ "dependencies": {
60
+ "form-data": "^4.0.0"
61
+ }
62
+ }