testomatio-reporter-cli 2.8.5-beta.2-yarn → 2.9.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/package.json +1 -1
- package/src/adapter/codecept.js +24 -2
- package/src/adapter/playwright.js +14 -6
- package/src/bin/cli.js +2 -2
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/constants.js +9 -0
- package/src/pipe/coverage.js +25 -7
- package/src/pipe/debug.js +35 -36
- package/src/pipe/testomatio.js +22 -13
- package/src/replay.js +2 -2
- package/src/xmlReader.js +4 -5
- package/types/types.d.ts +10 -3
package/package.json
CHANGED
package/src/adapter/codecept.js
CHANGED
|
@@ -48,6 +48,7 @@ if (MAJOR_VERSION === 3 && MINOR_VERSION < 7) {
|
|
|
48
48
|
|
|
49
49
|
function CodeceptReporter(config) {
|
|
50
50
|
const failedTests = [];
|
|
51
|
+
const reportedTestUids = new Set();
|
|
51
52
|
let videos = [];
|
|
52
53
|
let traces = [];
|
|
53
54
|
const reportTestPromises = [];
|
|
@@ -161,6 +162,7 @@ function CodeceptReporter(config) {
|
|
|
161
162
|
const error = hook?.ctx?.currentTest?.err;
|
|
162
163
|
|
|
163
164
|
for (const test of suite.tests) {
|
|
165
|
+
reportedTestUids.add(test.uid);
|
|
164
166
|
const reportTestPromise = client.addTestRun('failed', {
|
|
165
167
|
...stripExampleFromTitle(test.title),
|
|
166
168
|
rid: test.uid,
|
|
@@ -197,9 +199,29 @@ function CodeceptReporter(config) {
|
|
|
197
199
|
});
|
|
198
200
|
});
|
|
199
201
|
|
|
202
|
+
event.dispatcher.on(event.test.skipped, test => {
|
|
203
|
+
const { uid, tags, title } = test.simplify();
|
|
204
|
+
|
|
205
|
+
if (uid && reportedTestUids.has(uid)) return;
|
|
206
|
+
|
|
207
|
+
services.setContext(null);
|
|
208
|
+
|
|
209
|
+
const reportTestPromise = client.addTestRun(STATUS.SKIPPED, {
|
|
210
|
+
...stripExampleFromTitle(title),
|
|
211
|
+
rid: uid,
|
|
212
|
+
test_id: getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`),
|
|
213
|
+
suite_title: test.parent && stripTagsFromTitle(stripExampleFromTitle(test.parent.title).title),
|
|
214
|
+
time: test.duration,
|
|
215
|
+
meta: test.meta,
|
|
216
|
+
});
|
|
217
|
+
reportTestPromises.push(reportTestPromise);
|
|
218
|
+
reportedTestUids.add(uid);
|
|
219
|
+
});
|
|
220
|
+
|
|
200
221
|
event.dispatcher.on(event.test.after, test => {
|
|
201
222
|
const { uid, tags, title, artifacts } = test.simplify();
|
|
202
223
|
const error = test.err || null;
|
|
224
|
+
reportedTestUids.add(uid);
|
|
203
225
|
failedTests.push(uid || title);
|
|
204
226
|
const testObj = getTestAndMessage(title);
|
|
205
227
|
const files = buildArtifactFiles(artifacts);
|
|
@@ -211,8 +233,8 @@ function CodeceptReporter(config) {
|
|
|
211
233
|
|
|
212
234
|
// Build step hierarchy with screenshot from screenshotOnFail
|
|
213
235
|
const stepHierarchy = buildUnifiedStepHierarchy(
|
|
214
|
-
test.steps,
|
|
215
|
-
hookSteps,
|
|
236
|
+
test.steps,
|
|
237
|
+
hookSteps,
|
|
216
238
|
screenshotOnFailPath
|
|
217
239
|
);
|
|
218
240
|
|
|
@@ -14,13 +14,13 @@ import { fetchLinksFromLogs } from './utils/playwright.js';
|
|
|
14
14
|
import { formatStep, addStatusToStep, addArtifactsToStep } from './utils/step-formatter.js';
|
|
15
15
|
import { log } from '../utils/log.js';
|
|
16
16
|
|
|
17
|
-
const reportTestPromises = [];
|
|
18
|
-
|
|
19
17
|
class PlaywrightReporter {
|
|
20
18
|
constructor(config = {}) {
|
|
21
19
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
22
20
|
|
|
23
21
|
this.uploads = [];
|
|
22
|
+
this.reportTestPromises = [];
|
|
23
|
+
this.runPromise = Promise.resolve();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
onBegin(config, suite) {
|
|
@@ -29,7 +29,9 @@ class PlaywrightReporter {
|
|
|
29
29
|
if (!this.client) return;
|
|
30
30
|
this.suite = suite;
|
|
31
31
|
this.config = config;
|
|
32
|
-
this.
|
|
32
|
+
this.uploads = [];
|
|
33
|
+
this.reportTestPromises = [];
|
|
34
|
+
this.runPromise = this.client.createRun();
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
onTestBegin(testInfo) {
|
|
@@ -41,6 +43,7 @@ class PlaywrightReporter {
|
|
|
41
43
|
// test.parent.project().__projectId
|
|
42
44
|
|
|
43
45
|
if (!this.client) return;
|
|
46
|
+
await this.runPromise;
|
|
44
47
|
|
|
45
48
|
const { title } = test;
|
|
46
49
|
const { error, duration } = result;
|
|
@@ -133,7 +136,11 @@ class PlaywrightReporter {
|
|
|
133
136
|
...meta,
|
|
134
137
|
...project.metadata, // metadata has any type (in playwright), but we will stringify it in client.js
|
|
135
138
|
...test.annotations?.reduce((acc, annotation) => {
|
|
136
|
-
acc[annotation.type]
|
|
139
|
+
if (acc[annotation.type]) {
|
|
140
|
+
acc[annotation.type] = `${acc[annotation.type]}, ${annotation.description}`;
|
|
141
|
+
} else {
|
|
142
|
+
acc[annotation.type] = annotation.description;
|
|
143
|
+
}
|
|
137
144
|
return acc;
|
|
138
145
|
}, {}),
|
|
139
146
|
},
|
|
@@ -149,7 +156,7 @@ class PlaywrightReporter {
|
|
|
149
156
|
// remove empty uploads
|
|
150
157
|
this.uploads = this.uploads.filter(anUpload => anUpload.files.length);
|
|
151
158
|
|
|
152
|
-
reportTestPromises.push(reportTestPromise);
|
|
159
|
+
this.reportTestPromises.push(reportTestPromise);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
#getArtifactPath(artifact) {
|
|
@@ -173,7 +180,8 @@ class PlaywrightReporter {
|
|
|
173
180
|
async onEnd(result) {
|
|
174
181
|
if (!this.client) return;
|
|
175
182
|
|
|
176
|
-
await
|
|
183
|
+
await this.runPromise;
|
|
184
|
+
await Promise.all(this.reportTestPromises);
|
|
177
185
|
|
|
178
186
|
if (this.uploads.length) {
|
|
179
187
|
if (this.client.uploader.isEnabled) log.info(`🎞️ Uploading ${this.uploads.length} files...`);
|
package/src/bin/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { glob } from 'glob';
|
|
|
6
6
|
import createDebugMessages from 'debug';
|
|
7
7
|
import TestomatClient from '../client.js';
|
|
8
8
|
import XmlReader from '../xmlReader.js';
|
|
9
|
-
import { APP_PREFIX, STATUS, DEBUG_FILE } from '../constants.js';
|
|
9
|
+
import { APP_PREFIX, STATUS, DEBUG_FILE, BATCH_MODE } from '../constants.js';
|
|
10
10
|
import { cleanLatestRunId, getPackageVersion, applyFilter } from '../utils/utils.js';
|
|
11
11
|
import { config } from '../config.js';
|
|
12
12
|
import { readLatestRunId } from '../utils/utils.js';
|
|
@@ -369,7 +369,7 @@ program
|
|
|
369
369
|
const client = new TestomatClient({
|
|
370
370
|
apiKey,
|
|
371
371
|
runId,
|
|
372
|
-
|
|
372
|
+
batchMode: BATCH_MODE.DISABLED,
|
|
373
373
|
});
|
|
374
374
|
|
|
375
375
|
let testruns = client.uploader.readUploadedFiles(runId);
|
|
@@ -9,6 +9,7 @@ import { config } from '../config.js';
|
|
|
9
9
|
import { readLatestRunId } from '../utils/utils.js';
|
|
10
10
|
import dotenv from 'dotenv';
|
|
11
11
|
import { log } from '../utils/log.js';
|
|
12
|
+
import { BATCH_MODE } from '../constants.js';
|
|
12
13
|
|
|
13
14
|
const debug = createDebugMessages('@testomatio/reporter:upload-cli');
|
|
14
15
|
const version = getPackageVersion();
|
|
@@ -37,7 +38,7 @@ program
|
|
|
37
38
|
const client = new TestomatClient({
|
|
38
39
|
apiKey,
|
|
39
40
|
runId,
|
|
40
|
-
|
|
41
|
+
batchMode: BATCH_MODE.DISABLED,
|
|
41
42
|
});
|
|
42
43
|
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
43
44
|
|
package/src/constants.js
CHANGED
|
@@ -28,6 +28,14 @@ const STATUS = {
|
|
|
28
28
|
SKIPPED: 'skipped',
|
|
29
29
|
FINISHED: 'finished',
|
|
30
30
|
};
|
|
31
|
+
|
|
32
|
+
// batch upload mode
|
|
33
|
+
/** @type {{ AUTO: 'auto', MANUAL: 'manual', DISABLED: 'disabled' }} */
|
|
34
|
+
const BATCH_MODE = {
|
|
35
|
+
AUTO: 'auto',
|
|
36
|
+
MANUAL: 'manual',
|
|
37
|
+
DISABLED: 'disabled',
|
|
38
|
+
};
|
|
31
39
|
// html pipe var
|
|
32
40
|
const HTML_REPORT = {
|
|
33
41
|
FOLDER: 'html-report',
|
|
@@ -61,6 +69,7 @@ export {
|
|
|
61
69
|
TESTOMAT_TMP_STORAGE_DIR,
|
|
62
70
|
CSV_HEADERS,
|
|
63
71
|
STATUS,
|
|
72
|
+
BATCH_MODE,
|
|
64
73
|
HTML_REPORT,
|
|
65
74
|
MARKDOWN_REPORT,
|
|
66
75
|
REQUEST_TIMEOUT,
|
package/src/pipe/coverage.js
CHANGED
|
@@ -261,9 +261,10 @@ class CoveragePipe { // or Changes for the future???
|
|
|
261
261
|
*/
|
|
262
262
|
#getChangedFilesFromGit(cmd) {
|
|
263
263
|
try {
|
|
264
|
+
// Capture stderr (instead of ignoring it) so Git's actual error is available for diagnostics
|
|
264
265
|
const result = execSync(cmd, {
|
|
265
266
|
encoding: 'utf-8',
|
|
266
|
-
stdio: ['pipe', 'pipe', '
|
|
267
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
267
268
|
});
|
|
268
269
|
|
|
269
270
|
return result
|
|
@@ -272,16 +273,33 @@ class CoveragePipe { // or Changes for the future???
|
|
|
272
273
|
.filter(Boolean);
|
|
273
274
|
}
|
|
274
275
|
catch (err) {
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
// Prefer Git's own stderr output, fall back to the generic error message
|
|
277
|
+
const gitOutput = (err.stderr || '').toString().trim();
|
|
278
|
+
const errorMessage = gitOutput || err.message || '';
|
|
279
|
+
|
|
280
|
+
// Git edge: Not a git repository
|
|
277
281
|
if (errorMessage.includes('Not a git repository')) {
|
|
278
|
-
log.error(
|
|
282
|
+
log.error('❌ Error: This folder is not a Git repository.');
|
|
283
|
+
return [];
|
|
279
284
|
}
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
|
|
286
|
+
// Git edge: the branch/ref to diff against is not available locally.
|
|
287
|
+
// This is common in CI, where a shallow checkout fetches only the current branch.
|
|
288
|
+
if (
|
|
289
|
+
errorMessage.includes('unknown revision') ||
|
|
290
|
+
errorMessage.includes('ambiguous argument') ||
|
|
291
|
+
errorMessage.includes('bad revision')
|
|
292
|
+
) {
|
|
293
|
+
log.error(`❌ Git command failed ("${cmd}"):\n${errorMessage}`);
|
|
294
|
+
log.error(
|
|
295
|
+
`🔍 Branch "${this.branch}" was not found locally. ` +
|
|
296
|
+
`In CI this usually means a shallow checkout — fetch full history first, e.g. ` +
|
|
297
|
+
`actions/checkout with "fetch-depth: 0", or run "git fetch origin ${this.branch}:${this.branch}".`
|
|
298
|
+
);
|
|
299
|
+
return [];
|
|
282
300
|
}
|
|
283
301
|
|
|
284
|
-
|
|
302
|
+
throw new Error(`❌ Git command failed ("${cmd}"):\n${errorMessage}`);
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
305
|
|
package/src/pipe/debug.js
CHANGED
|
@@ -14,13 +14,7 @@ export class DebugPipe {
|
|
|
14
14
|
|
|
15
15
|
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
16
16
|
if (this.isEnabled) {
|
|
17
|
-
this.
|
|
18
|
-
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD,
|
|
19
|
-
intervalFunction: null,
|
|
20
|
-
intervalTime: 5000,
|
|
21
|
-
tests: [],
|
|
22
|
-
batchIndex: 0,
|
|
23
|
-
};
|
|
17
|
+
this.tests = [];
|
|
24
18
|
const suffix = process.env.TESTOMATIO_REPLAY ? 'replay' : '';
|
|
25
19
|
const paths = getDebugFilePath(suffix);
|
|
26
20
|
this.logFilePath = paths.tmp;
|
|
@@ -60,8 +54,13 @@ export class DebugPipe {
|
|
|
60
54
|
this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
|
|
61
55
|
this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
|
|
62
56
|
this.logToFile({ data: 'store', store: this.store || {} });
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
|
|
58
|
+
// Safety net for hook failures (e.g. a failing AfterSuite) that abort the run
|
|
59
|
+
// before finishRun: buffered tests would otherwise be lost. The handler is
|
|
60
|
+
// attached lazily when the first test is buffered and detached once flushed,
|
|
61
|
+
// so processes that create many pipes don't pile up `exit` listeners.
|
|
62
|
+
this.flushOnExit = () => this.flushBufferedTests();
|
|
63
|
+
this.exitListenerAttached = false;
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
|
|
@@ -88,42 +87,22 @@ export class DebugPipe {
|
|
|
88
87
|
|
|
89
88
|
async createRun(params = {}) {
|
|
90
89
|
if (!this.isEnabled) return;
|
|
91
|
-
if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
|
|
92
|
-
|
|
93
|
-
if (!this.isEnabled) return {};
|
|
94
|
-
if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
|
|
95
90
|
|
|
96
91
|
this.logToFile({ action: 'createRun', params });
|
|
97
92
|
}
|
|
98
93
|
|
|
99
94
|
async addTest(data) {
|
|
100
95
|
if (!this.isEnabled) return;
|
|
101
|
-
|
|
102
|
-
if (!this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else this.batch.tests.push(data);
|
|
107
|
-
|
|
108
|
-
if (!this.batch.intervalFunction) await this.batchUpload();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async batchUpload() {
|
|
112
|
-
this.batch.batchIndex++;
|
|
113
|
-
if (!this.batch.isEnabled) return;
|
|
114
|
-
if (!this.batch.tests.length) return;
|
|
115
|
-
|
|
116
|
-
const testsToSend = this.batch.tests.splice(0);
|
|
117
|
-
|
|
118
|
-
const logData = { action: 'addTestsBatch', tests: testsToSend };
|
|
119
|
-
if (this.store.runId) logData.runId = this.store.runId;
|
|
120
|
-
this.logToFile(logData);
|
|
96
|
+
this.tests.push(data);
|
|
97
|
+
if (!this.exitListenerAttached) {
|
|
98
|
+
process.once('exit', this.flushOnExit);
|
|
99
|
+
this.exitListenerAttached = true;
|
|
100
|
+
}
|
|
121
101
|
}
|
|
122
102
|
|
|
123
103
|
async finishRun(params) {
|
|
124
104
|
if (!this.isEnabled) return;
|
|
125
105
|
await this.sync();
|
|
126
|
-
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
127
106
|
const logData = { action: 'finishRun', params };
|
|
128
107
|
if (this.store.runId) logData.runId = this.store.runId;
|
|
129
108
|
this.logToFile(logData);
|
|
@@ -133,8 +112,28 @@ export class DebugPipe {
|
|
|
133
112
|
}
|
|
134
113
|
|
|
135
114
|
async sync() {
|
|
136
|
-
|
|
137
|
-
|
|
115
|
+
this.flushBufferedTests();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Writes any buffered tests to the debug file as a single batch.
|
|
120
|
+
* Runs synchronously so it can also be invoked from a process `exit` handler,
|
|
121
|
+
* which is the only chance to persist tests when a hook failure (e.g. a failing
|
|
122
|
+
* AfterSuite) prevents `finishRun` from being reached. Idempotent: the buffer is
|
|
123
|
+
* drained on flush, so a later `finishRun`/exit flush is a no-op.
|
|
124
|
+
*/
|
|
125
|
+
flushBufferedTests() {
|
|
126
|
+
if (!this.isEnabled || !this.tests.length) return;
|
|
127
|
+
|
|
128
|
+
const tests = this.tests.splice(0);
|
|
129
|
+
const logData = { action: 'addTestsBatch', tests };
|
|
130
|
+
if (this.store.runId) logData.runId = this.store.runId;
|
|
131
|
+
this.logToFile(logData);
|
|
132
|
+
|
|
133
|
+
if (this.exitListenerAttached) {
|
|
134
|
+
process.removeListener('exit', this.flushOnExit);
|
|
135
|
+
this.exitListenerAttached = false;
|
|
136
|
+
}
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
toString() {
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -5,6 +5,7 @@ import JsonCycle from 'json-cycle';
|
|
|
5
5
|
import {
|
|
6
6
|
APP_PREFIX,
|
|
7
7
|
STATUS,
|
|
8
|
+
BATCH_MODE,
|
|
8
9
|
REQUEST_TIMEOUT,
|
|
9
10
|
getCreateRunRequestTimeout,
|
|
10
11
|
REPORTER_REQUEST_RETRIES,
|
|
@@ -46,17 +47,25 @@ function parseCiParams(raw) {
|
|
|
46
47
|
/**
|
|
47
48
|
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
48
49
|
* @typedef {import('../../types/types.js').TestData} TestData
|
|
50
|
+
* @typedef {import('../../types/types.js').BatchMode} BatchMode
|
|
51
|
+
* @typedef {import('../../types/types.js').CreateRunParams} CreateRunParams
|
|
49
52
|
* @class TestomatioPipe
|
|
50
53
|
* @implements {Pipe}
|
|
51
54
|
*/
|
|
52
55
|
class TestomatioPipe {
|
|
53
56
|
constructor(params, store) {
|
|
54
57
|
this.batch = {
|
|
55
|
-
|
|
58
|
+
/** @type {BatchMode}
|
|
59
|
+
* Batch upload mode:
|
|
60
|
+
* - `auto`: upload tests automatically by time interval (e.g. every 5 seconds).
|
|
61
|
+
* - `manual`: buffer tests and upload only when `sync()` is invoked manually.
|
|
62
|
+
* - `disabled`: send one test per request, no batching.
|
|
63
|
+
*/
|
|
64
|
+
mode: params.batchMode || (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? BATCH_MODE.DISABLED : BATCH_MODE.AUTO),
|
|
56
65
|
intervalFunction: null, // will be created in createRun by setInterval function
|
|
57
|
-
intervalTime:
|
|
66
|
+
intervalTime: 6000, // how often tests are sent
|
|
58
67
|
tests: [], // array of tests in batch
|
|
59
|
-
batchIndex: 0,
|
|
68
|
+
batchIndex: 0, // represents the current batch index (starts from 1 and increments by 1 for each batch)
|
|
60
69
|
numberOfTimesCalledWithoutTests: 0, // how many times batch was called without tests
|
|
61
70
|
};
|
|
62
71
|
this.retriesTimestamps = [];
|
|
@@ -222,13 +231,13 @@ class TestomatioPipe {
|
|
|
222
231
|
|
|
223
232
|
/**
|
|
224
233
|
* Creates a new run on Testomat.io
|
|
225
|
-
* @param {
|
|
234
|
+
* @param {CreateRunParams} params
|
|
226
235
|
* @returns Promise<void>
|
|
227
236
|
*/
|
|
228
237
|
async createRun(params = {}) {
|
|
229
|
-
this.batch.
|
|
238
|
+
if (params.batchMode) this.batch.mode = params.batchMode;
|
|
230
239
|
if (!this.isEnabled) return;
|
|
231
|
-
if (this.batch.
|
|
240
|
+
if (this.batch.mode === BATCH_MODE.AUTO && this.isEnabled)
|
|
232
241
|
this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
|
|
233
242
|
if (this.store) {
|
|
234
243
|
this.store.runKind = params.kind;
|
|
@@ -433,14 +442,14 @@ class TestomatioPipe {
|
|
|
433
442
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
434
443
|
*/
|
|
435
444
|
#batchUpload = async () => {
|
|
436
|
-
if (
|
|
445
|
+
if (this.batch.mode === BATCH_MODE.DISABLED) return;
|
|
437
446
|
if (!this.batch.tests.length) return;
|
|
438
447
|
if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
|
|
439
448
|
// prevent infinite loop
|
|
440
449
|
if (this.batch.numberOfTimesCalledWithoutTests > 10) {
|
|
441
450
|
debug('📨 Batch upload: no tests to send for 10 times, stopping batch');
|
|
442
451
|
clearInterval(this.batch.intervalFunction);
|
|
443
|
-
this.batch.
|
|
452
|
+
this.batch.mode = BATCH_MODE.DISABLED;
|
|
444
453
|
}
|
|
445
454
|
if (!this.batch.tests.length) {
|
|
446
455
|
debug('📨 Batch upload: no tests to send');
|
|
@@ -496,11 +505,11 @@ class TestomatioPipe {
|
|
|
496
505
|
this.#formatData(data);
|
|
497
506
|
|
|
498
507
|
let uploading = null;
|
|
499
|
-
if (
|
|
508
|
+
if (this.batch.mode === BATCH_MODE.DISABLED) uploading = this.#uploadSingleTest(data);
|
|
500
509
|
else this.batch.tests.push(data);
|
|
501
510
|
|
|
502
|
-
//
|
|
503
|
-
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
511
|
+
// auto mode but no interval running yet (e.g. createRun hasn't started it): flush immediately
|
|
512
|
+
if (this.batch.mode === BATCH_MODE.AUTO && !this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
504
513
|
|
|
505
514
|
// return promise to be able to wait for it
|
|
506
515
|
return uploading;
|
|
@@ -529,7 +538,7 @@ class TestomatioPipe {
|
|
|
529
538
|
// (e.g. if test has artifacts, add test function will be invoked only after artifacts are uploaded)
|
|
530
539
|
// batch stops working after run is finished; thus, disable it to use single test uploading
|
|
531
540
|
this.batch.intervalFunction = null;
|
|
532
|
-
this.batch.
|
|
541
|
+
this.batch.mode = BATCH_MODE.DISABLED;
|
|
533
542
|
}
|
|
534
543
|
|
|
535
544
|
debug('Finishing run...');
|
|
@@ -613,7 +622,7 @@ class TestomatioPipe {
|
|
|
613
622
|
if (this.batch.intervalFunction) {
|
|
614
623
|
clearInterval(this.batch.intervalFunction);
|
|
615
624
|
this.batch.intervalFunction = null;
|
|
616
|
-
this.batch.
|
|
625
|
+
this.batch.mode = BATCH_MODE.DISABLED;
|
|
617
626
|
}
|
|
618
627
|
this.batch.tests = [];
|
|
619
628
|
}
|
package/src/replay.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import TestomatClient from './client.js';
|
|
4
|
-
import { STATUS, DEBUG_FILE } from './constants.js';
|
|
4
|
+
import { STATUS, DEBUG_FILE, BATCH_MODE } from './constants.js';
|
|
5
5
|
import { config } from './config.js';
|
|
6
6
|
|
|
7
7
|
export class Replay {
|
|
@@ -216,7 +216,7 @@ export class Replay {
|
|
|
216
216
|
|
|
217
217
|
const client = new TestomatClient({
|
|
218
218
|
apiKey: this.apiKey,
|
|
219
|
-
|
|
219
|
+
batchMode: BATCH_MODE.AUTO,
|
|
220
220
|
...runParams,
|
|
221
221
|
...(runId && { runId }),
|
|
222
222
|
});
|
package/src/xmlReader.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
-
import { APP_PREFIX, STATUS } from './constants.js';
|
|
6
|
+
import { APP_PREFIX, STATUS, BATCH_MODE } from './constants.js';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { NUnitXmlParser } from './junit-adapter/nunit-parser.js';
|
|
@@ -73,8 +73,7 @@ class XmlReader {
|
|
|
73
73
|
env: TESTOMATIO_ENV,
|
|
74
74
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
75
75
|
detach: TESTOMATIO_MARK_DETACHED,
|
|
76
|
-
|
|
77
|
-
isBatchEnabled: false,
|
|
76
|
+
batchMode: BATCH_MODE.MANUAL,
|
|
78
77
|
};
|
|
79
78
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
80
79
|
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
|
|
@@ -543,7 +542,7 @@ class XmlReader {
|
|
|
543
542
|
title: this.requestParams.title,
|
|
544
543
|
env: this.requestParams.env,
|
|
545
544
|
group_title: this.requestParams.group_title,
|
|
546
|
-
|
|
545
|
+
batchMode: this.requestParams.batchMode,
|
|
547
546
|
};
|
|
548
547
|
|
|
549
548
|
debug('Run', runParams);
|
|
@@ -611,7 +610,7 @@ class XmlReader {
|
|
|
611
610
|
this.pipes = this.pipes || (await this.pipesPromise);
|
|
612
611
|
|
|
613
612
|
// Create run before uploading tests to ensure runId is set
|
|
614
|
-
await this.createRun();
|
|
613
|
+
// await this.createRun(); // makes reporting stuck after finish, thus commenting out
|
|
615
614
|
|
|
616
615
|
if (!this.tests || !Array.isArray(this.tests) || this.tests.length === 0) {
|
|
617
616
|
debug('No tests to upload, finishing run');
|
package/types/types.d.ts
CHANGED
|
@@ -234,7 +234,7 @@ export interface HtmlTestData extends TestData {
|
|
|
234
234
|
/**
|
|
235
235
|
* Extended test data for Markdown reporter.
|
|
236
236
|
*/
|
|
237
|
-
export interface MarkdownTestData extends HtmlTestData {}
|
|
237
|
+
export interface MarkdownTestData extends HtmlTestData { }
|
|
238
238
|
|
|
239
239
|
/**
|
|
240
240
|
* Object representing a result of a Run.
|
|
@@ -288,6 +288,13 @@ export enum RunStatus {
|
|
|
288
288
|
Finished = 'finished',
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
/** Batch upload strategy:
|
|
292
|
+
* `auto` (by time interval, e.g. every 5 seconds),
|
|
293
|
+
* `manual` (send tests via manually invoking sync() ),
|
|
294
|
+
* `disabled` (one test per request, no batching).
|
|
295
|
+
*/
|
|
296
|
+
export type BatchMode = 'auto' | 'manual' | 'disabled';
|
|
297
|
+
|
|
291
298
|
export interface Pipe {
|
|
292
299
|
isEnabled: boolean;
|
|
293
300
|
store: {};
|
|
@@ -334,8 +341,8 @@ export interface CreateRunParams {
|
|
|
334
341
|
/** Run configuration merged into the server-side run configuration. */
|
|
335
342
|
configuration?: Record<string, any>;
|
|
336
343
|
|
|
337
|
-
/** Override batch upload
|
|
338
|
-
|
|
344
|
+
/** Override batch upload mode. */
|
|
345
|
+
batchMode?: BatchMode;
|
|
339
346
|
}
|
|
340
347
|
|
|
341
348
|
/**
|