@testomatio/reporter 2.19.0 → 2.20.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/lib/adapter/codecept.js +1 -0
- package/lib/adapter/cucumber/current.js +1 -0
- package/lib/adapter/cypress-plugin/index.js +1 -0
- package/lib/adapter/e2e.d.ts +49 -0
- package/lib/adapter/e2e.js +250 -0
- package/lib/adapter/jasmine.js +1 -0
- package/lib/adapter/jest.js +1 -0
- package/lib/adapter/mocha.d.ts +4 -1
- package/lib/adapter/mocha.js +123 -122
- package/lib/adapter/nightwatch.js +1 -0
- package/lib/adapter/playwright.js +1 -0
- package/lib/adapter/vitest.js +1 -0
- package/lib/adapter/webdriver.js +1 -0
- package/lib/allureReader.d.ts +4 -1
- package/lib/allureReader.js +2 -1
- package/lib/bin/cli.js +32 -13
- package/lib/client.d.ts +1 -0
- package/lib/client.js +12 -3
- package/lib/data-storage.js +4 -3
- package/lib/pipe/testomatio.js +17 -1
- package/lib/replay.js +1 -0
- package/lib/uploader.d.ts +1 -0
- package/lib/uploader.js +21 -1
- package/lib/utils/pipe_utils.d.ts +16 -3
- package/lib/utils/pipe_utils.js +45 -4
- package/lib/xmlReader.js +2 -0
- package/package.json +7 -2
- package/src/adapter/codecept.js +2 -0
- package/src/adapter/cucumber/current.js +2 -0
- package/src/adapter/cypress-plugin/index.js +2 -0
- package/src/adapter/e2e.js +245 -0
- package/src/adapter/jasmine.js +2 -0
- package/src/adapter/jest.js +2 -0
- package/src/adapter/mocha.js +146 -145
- package/src/adapter/nightwatch.js +2 -0
- package/src/adapter/playwright.js +2 -0
- package/src/adapter/vitest.js +2 -0
- package/src/adapter/webdriver.js +2 -0
- package/src/allureReader.js +2 -1
- package/src/bin/cli.js +30 -11
- package/src/client.js +11 -3
- package/src/data-storage.js +4 -3
- package/src/pipe/testomatio.js +27 -1
- package/src/replay.js +1 -0
- package/src/uploader.js +30 -1
- package/src/utils/pipe_utils.js +45 -4
- package/src/xmlReader.js +3 -0
- package/types/types.d.ts +1 -0
package/lib/allureReader.d.ts
CHANGED
package/lib/allureReader.js
CHANGED
|
@@ -163,7 +163,8 @@ class AllureReader {
|
|
|
163
163
|
steps: this.convertSteps(result.steps || [], resultsDir),
|
|
164
164
|
message: result.statusDetails?.message || '',
|
|
165
165
|
stack: result.statusDetails?.trace || '',
|
|
166
|
-
meta
|
|
166
|
+
// meta must always include reporter version and adapter used
|
|
167
|
+
meta: { ...this.extractMeta(result), reporterVersion: this.version, adapter: 'allure' },
|
|
167
168
|
links: this.extractLinks(result),
|
|
168
169
|
artifacts: [],
|
|
169
170
|
create: true,
|
package/lib/bin/cli.js
CHANGED
|
@@ -121,35 +121,54 @@ program
|
|
|
121
121
|
program
|
|
122
122
|
.command('fetch')
|
|
123
123
|
.description('Fetch runs from Testomat.io API v2')
|
|
124
|
+
.argument('[url]', 'URL of a run or of a (filtered) runs page in Testomat.io')
|
|
124
125
|
.option('--project <slug>', 'Project slug (or set TESTOMATIO_PROJECT)')
|
|
126
|
+
.option('--run <id>', 'Fetch a single run by its id')
|
|
125
127
|
.option('--title <text>', 'Filter by run title')
|
|
126
128
|
.option('--tql <query>', 'Filter using Testomat Query Language')
|
|
127
129
|
.option('--rungroup <uid>', 'Filter by rungroup id')
|
|
128
130
|
.option('--limit <number>', `Max number of runs to return (max ${FETCH_RUNS_MAX_LIMIT})`, String(FETCH_RUNS_DEFAULT_LIMIT))
|
|
129
131
|
.option('--latest', 'Only fetch the most recent run (shorthand for --limit 1)')
|
|
130
132
|
.option('--format <format>', 'Machine-readable output: run ids, one per line (--format id) or run details (--format json)')
|
|
131
|
-
.action(async (opts) => {
|
|
133
|
+
.action(async (pageUrl, opts) => {
|
|
132
134
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
133
135
|
if (!apiKey) {
|
|
134
136
|
log_js_1.log.error(picocolors_1.default.red('⚠️ TESTOMATIO API key required'));
|
|
135
137
|
process.exit(1);
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
let page = {};
|
|
140
|
+
if (pageUrl) {
|
|
141
|
+
page = (0, pipe_utils_js_1.parseRunsUrl)(pageUrl);
|
|
142
|
+
if (!page) {
|
|
143
|
+
log_js_1.log.error(picocolors_1.default.red(`⚠️ Not a Testomat.io run or runs page URL: ${pageUrl}`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const project = opts.project || page.project || process.env.TESTOMATIO_PROJECT;
|
|
138
148
|
if (!project) {
|
|
139
149
|
log_js_1.log.error(picocolors_1.default.red('⚠️ --project (or TESTOMATIO_PROJECT) is required'));
|
|
140
150
|
process.exit(1);
|
|
141
151
|
}
|
|
142
|
-
const baseUrl = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
url
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
const baseUrl = page.baseUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
153
|
+
const runId = opts.run || page.runId;
|
|
154
|
+
let url;
|
|
155
|
+
if (runId) {
|
|
156
|
+
url = new URL(`/api/v2/${project}/runs/${runId}`, baseUrl);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
url = new URL(`/api/v2/${project}/runs`, baseUrl);
|
|
160
|
+
for (const [key, value] of page.params || [])
|
|
161
|
+
url.searchParams.append(key, value);
|
|
162
|
+
if (opts.title)
|
|
163
|
+
url.searchParams.set('search', opts.title);
|
|
164
|
+
if (opts.tql)
|
|
165
|
+
url.searchParams.set('tql', opts.tql);
|
|
166
|
+
if (opts.rungroup)
|
|
167
|
+
url.searchParams.set('groupId', opts.rungroup);
|
|
168
|
+
const requestedLimit = opts.latest ? 1 : parseInt(opts.limit, 10) || FETCH_RUNS_DEFAULT_LIMIT;
|
|
169
|
+
const limit = Math.min(Math.max(requestedLimit, 1), FETCH_RUNS_MAX_LIMIT);
|
|
170
|
+
url.searchParams.set('per_page', String(limit));
|
|
171
|
+
}
|
|
153
172
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
154
173
|
const body = await response.json();
|
|
155
174
|
if (!response.ok) {
|
package/lib/client.d.ts
CHANGED
package/lib/client.js
CHANGED
|
@@ -37,6 +37,8 @@ class Client {
|
|
|
37
37
|
this.pipeStore = {};
|
|
38
38
|
this.runId = '';
|
|
39
39
|
this.queue = Promise.resolve();
|
|
40
|
+
// adapter name is set by the adapter module itself on load (global.TESTOMATIO_ADAPTER)
|
|
41
|
+
this.adapterName = params.adapter || global.TESTOMATIO_ADAPTER;
|
|
40
42
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
41
43
|
const pathToPackageJSON = path_1.default.join(__dirname, '../package.json');
|
|
42
44
|
try {
|
|
@@ -214,7 +216,9 @@ class Client {
|
|
|
214
216
|
const uploadedFiles = [];
|
|
215
217
|
const stackArtifactsEnabled = (0, utils_js_1.transformEnvVarToBoolean)(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
216
218
|
const { time = 0, example = null, filesBuffers = [], code = null, file, suite_id, test_id, timestamp, links, overwrite, tags, } = testData;
|
|
217
|
-
let { files = [], manuallyAttachedArtifacts, message = ''
|
|
219
|
+
let { files = [], manuallyAttachedArtifacts, message = '' } = testData;
|
|
220
|
+
/** @type {Record<string, any>} */
|
|
221
|
+
let meta = testData.meta || {};
|
|
218
222
|
if (stepArtifactPaths.size) {
|
|
219
223
|
const isStepArtifact = item => stepArtifactPaths.has(typeof item === 'object' ? item?.path : item);
|
|
220
224
|
files = files.filter(f => !isStepArtifact(f));
|
|
@@ -229,6 +233,11 @@ class Client {
|
|
|
229
233
|
acc[key] = value;
|
|
230
234
|
return acc;
|
|
231
235
|
}, {});
|
|
236
|
+
// meta must always include reporter version and adapter used
|
|
237
|
+
if (this.version)
|
|
238
|
+
meta.reporterVersion = this.version;
|
|
239
|
+
if (this.adapterName)
|
|
240
|
+
meta.adapter = this.adapterName;
|
|
232
241
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
233
242
|
let errorFormatted = '';
|
|
234
243
|
if (error) {
|
|
@@ -335,8 +344,8 @@ class Client {
|
|
|
335
344
|
this.queue = this.queue
|
|
336
345
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
337
346
|
.then(() => {
|
|
338
|
-
|
|
339
|
-
|
|
347
|
+
// no early return when uploading is disabled:
|
|
348
|
+
// skipped artifacts (e.g. no S3 credentials obtained) must still be reported to the user
|
|
340
349
|
const filesizeStrMaxLength = 7;
|
|
341
350
|
if (this.uploader.successfulUploads.length) {
|
|
342
351
|
debug('\n', constants_js_1.APP_PREFIX, `🗄️ ${this.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
|
package/lib/data-storage.js
CHANGED
|
@@ -220,9 +220,10 @@ class DataStorage {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
function stringToMD5Hash(str) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
// sha256 instead of md5 for FIPS-enabled hosts compatibility
|
|
224
|
+
const sha256 = crypto_1.default.createHash('sha256');
|
|
225
|
+
sha256.update(str);
|
|
226
|
+
const hash = sha256.digest('hex');
|
|
226
227
|
return `${process.env.runId || 'run'}_${hash}`;
|
|
227
228
|
}
|
|
228
229
|
exports.dataStorage = DataStorage.getInstance();
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -317,6 +317,9 @@ class TestomatioPipe {
|
|
|
317
317
|
(0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
|
|
318
318
|
this.#startS3CredentialsRefresh(runParams);
|
|
319
319
|
}
|
|
320
|
+
else {
|
|
321
|
+
this.#warnNoS3Credentials();
|
|
322
|
+
}
|
|
320
323
|
if (resp.data.url) {
|
|
321
324
|
const respUrl = new URL(resp.data.url);
|
|
322
325
|
this.runUrl = `${this.url}${respUrl.pathname}`;
|
|
@@ -345,6 +348,9 @@ class TestomatioPipe {
|
|
|
345
348
|
(0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
|
|
346
349
|
this.#startS3CredentialsRefresh(runParams);
|
|
347
350
|
}
|
|
351
|
+
else {
|
|
352
|
+
this.#warnNoS3Credentials();
|
|
353
|
+
}
|
|
348
354
|
this.store.runUrl = this.runUrl;
|
|
349
355
|
this.store.runPublicUrl = this.runPublicUrl;
|
|
350
356
|
this.store.runId = this.runId;
|
|
@@ -403,9 +409,19 @@ class TestomatioPipe {
|
|
|
403
409
|
}
|
|
404
410
|
}
|
|
405
411
|
catch (err) {
|
|
406
|
-
|
|
412
|
+
log_js_1.log.warn('⚠️ Failed to refresh S3 credentials:', err.message || err, '\nArtifact uploads may fail once the current credentials expire.');
|
|
407
413
|
}
|
|
408
414
|
};
|
|
415
|
+
/**
|
|
416
|
+
* Warns the user when Testomat.io did not return S3 credentials for artifacts upload.
|
|
417
|
+
* Skipped when artifacts uploading was intentionally disabled or a custom S3 storage is configured via env.
|
|
418
|
+
*/
|
|
419
|
+
#warnNoS3Credentials() {
|
|
420
|
+
if (process.env.TESTOMATIO_DISABLE_ARTIFACTS || process.env.S3_BUCKET)
|
|
421
|
+
return;
|
|
422
|
+
const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${this.runId || '<run_id>'} npx @testomatio/reporter upload-artifacts`;
|
|
423
|
+
log_js_1.log.warn(`⚠️ ${picocolors_1.default.yellow('Testomat.io did not return S3 credentials for artifacts upload.')}`, 'Artifacts will not be uploaded.', 'Please check the artifact sharing settings of your project.', `\nRun "${picocolors_1.default.magenta(command)}" later with valid S3 credentials to re-upload artifacts to this run.`);
|
|
424
|
+
}
|
|
409
425
|
#stopS3CredentialsRefresh() {
|
|
410
426
|
if (this.s3CredentialsRefreshTimer) {
|
|
411
427
|
clearInterval(this.s3CredentialsRefreshTimer);
|
package/lib/replay.js
CHANGED
package/lib/uploader.d.ts
CHANGED
package/lib/uploader.js
CHANGED
|
@@ -26,6 +26,7 @@ class S3Uploader {
|
|
|
26
26
|
*/
|
|
27
27
|
this.skippedUploads = [];
|
|
28
28
|
this.failedUploads = [];
|
|
29
|
+
this.warnedDisabledUpload = false;
|
|
29
30
|
/**
|
|
30
31
|
* @type {{path: string, size: number, link: string}[]}
|
|
31
32
|
*/
|
|
@@ -84,6 +85,22 @@ class S3Uploader {
|
|
|
84
85
|
disableLogStorage() {
|
|
85
86
|
this.storeEnabled = false;
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Warns once when artifact uploading is disabled not intentionally by the user,
|
|
90
|
+
* e.g. S3 credentials were not obtained from Testomat.io and no custom S3 storage is configured.
|
|
91
|
+
* Without this warning artifacts are skipped silently.
|
|
92
|
+
*/
|
|
93
|
+
#warnDisabledUpload(runId) {
|
|
94
|
+
if (this.warnedDisabledUpload)
|
|
95
|
+
return;
|
|
96
|
+
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
|
|
97
|
+
// uploading was intentionally disabled by the user or a custom S3 storage is used, nothing to warn about
|
|
98
|
+
if (TESTOMATIO_DISABLE_ARTIFACTS || S3_BUCKET)
|
|
99
|
+
return;
|
|
100
|
+
this.warnedDisabledUpload = true;
|
|
101
|
+
const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${runId || '<run_id>'} npx @testomatio/reporter upload-artifacts`;
|
|
102
|
+
log_js_1.log.warn(`🗄️ ${picocolors_1.default.yellow('Artifacts uploading is disabled: S3 credentials were not obtained from Testomat.io')}`, picocolors_1.default.yellow('(and no custom S3 storage is configured).'), 'Artifacts will not be uploaded.', `\nRun "${picocolors_1.default.magenta(command)}" later with valid S3 credentials to re-upload artifacts to this run.`);
|
|
103
|
+
}
|
|
87
104
|
/**
|
|
88
105
|
*
|
|
89
106
|
* @param {*} Body
|
|
@@ -202,6 +219,7 @@ class S3Uploader {
|
|
|
202
219
|
debug(`File ${filePath} does not exist`);
|
|
203
220
|
}
|
|
204
221
|
if (!this.isEnabled) {
|
|
222
|
+
this.#warnDisabledUpload(runId);
|
|
205
223
|
this.storeUploadedFile(filePath, runId, rid, false);
|
|
206
224
|
this.skippedUploads.push({ path: filePath, size: fileSize });
|
|
207
225
|
return;
|
|
@@ -239,8 +257,10 @@ class S3Uploader {
|
|
|
239
257
|
/* WDIO: some artifacts uploading started before createRun function completion
|
|
240
258
|
probably, the reason is that run is NOT created in adapter (but via cli) */
|
|
241
259
|
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
242
|
-
if (!this.isEnabled)
|
|
260
|
+
if (!this.isEnabled) {
|
|
261
|
+
this.#warnDisabledUpload(pathInS3[0]);
|
|
243
262
|
return;
|
|
263
|
+
}
|
|
244
264
|
let Key = pathInS3.filter(p => !!p).join('/');
|
|
245
265
|
const ext = this.#getFileExtBase64(buffer.toString('base64'));
|
|
246
266
|
if (ext) {
|
|
@@ -139,16 +139,29 @@ export function formatRunOutput(store: {
|
|
|
139
139
|
}, format?: string): string;
|
|
140
140
|
/**
|
|
141
141
|
* Format the runs fetched via the `fetch` command for machine-readable output.
|
|
142
|
-
* `json` prints the full array of run objects; `id` prints one run id per line;
|
|
142
|
+
* `json` prints the full array of run objects (a single object for one run); `id` prints one run id per line;
|
|
143
143
|
* any other format prints a human-readable multi-line summary per run.
|
|
144
144
|
*
|
|
145
|
-
* @param {{data?: Array}} body - Parsed response body from GET /api/v2/:project/runs.
|
|
145
|
+
* @param {{data?: Array|Object}} body - Parsed response body from GET /api/v2/:project/runs[/:id].
|
|
146
146
|
* @param {string} [format] - Value of the CLI `--format` option.
|
|
147
147
|
* @returns {string}
|
|
148
148
|
*/
|
|
149
149
|
export function formatFetchRunsOutput(body: {
|
|
150
|
-
data?: any[];
|
|
150
|
+
data?: any[] | any;
|
|
151
151
|
}, format?: string): string;
|
|
152
|
+
/**
|
|
153
|
+
* Parse a Testomat.io app URL of a run (/projects/:project/runs/:id)
|
|
154
|
+
* or of a runs page (/projects/:project/runs?filterParam=...) into API v2 request parts.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} pageUrl
|
|
157
|
+
* @returns {{baseUrl: string, project: string, runId?: string, params: string[][]}|null}
|
|
158
|
+
*/
|
|
159
|
+
export function parseRunsUrl(pageUrl: string): {
|
|
160
|
+
baseUrl: string;
|
|
161
|
+
project: string;
|
|
162
|
+
runId?: string;
|
|
163
|
+
params: string[][];
|
|
164
|
+
} | null;
|
|
152
165
|
/**
|
|
153
166
|
* Calculate the approximate size of data in bytes (JSON stringified, UTF-8 encoded length).
|
|
154
167
|
* @param {Object} data - Data to measure
|
package/lib/utils/pipe_utils.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.parsePipeOptions = parsePipeOptions;
|
|
|
18
18
|
exports.formatFilterListIds = formatFilterListIds;
|
|
19
19
|
exports.formatRunOutput = formatRunOutput;
|
|
20
20
|
exports.formatFetchRunsOutput = formatFetchRunsOutput;
|
|
21
|
+
exports.parseRunsUrl = parseRunsUrl;
|
|
21
22
|
exports.getObjectSize = getObjectSize;
|
|
22
23
|
exports.splitTestsIntoChunks = splitTestsIntoChunks;
|
|
23
24
|
const humanize_duration_1 = __importDefault(require("humanize-duration"));
|
|
@@ -362,17 +363,18 @@ function formatRunOutput(store, format) {
|
|
|
362
363
|
}
|
|
363
364
|
/**
|
|
364
365
|
* Format the runs fetched via the `fetch` command for machine-readable output.
|
|
365
|
-
* `json` prints the full array of run objects; `id` prints one run id per line;
|
|
366
|
+
* `json` prints the full array of run objects (a single object for one run); `id` prints one run id per line;
|
|
366
367
|
* any other format prints a human-readable multi-line summary per run.
|
|
367
368
|
*
|
|
368
|
-
* @param {{data?: Array}} body - Parsed response body from GET /api/v2/:project/runs.
|
|
369
|
+
* @param {{data?: Array|Object}} body - Parsed response body from GET /api/v2/:project/runs[/:id].
|
|
369
370
|
* @param {string} [format] - Value of the CLI `--format` option.
|
|
370
371
|
* @returns {string}
|
|
371
372
|
*/
|
|
372
373
|
function formatFetchRunsOutput(body, format) {
|
|
373
|
-
const
|
|
374
|
+
const data = body?.data || [];
|
|
375
|
+
const runs = [data].flat();
|
|
374
376
|
if (format === 'json')
|
|
375
|
-
return JSON.stringify(
|
|
377
|
+
return JSON.stringify(data, null, 2);
|
|
376
378
|
if (format === 'id')
|
|
377
379
|
return runs.map(run => run.id).filter(Boolean).join('\n');
|
|
378
380
|
return runs
|
|
@@ -380,6 +382,8 @@ function formatFetchRunsOutput(body, format) {
|
|
|
380
382
|
const lines = [`* ID: ${run.id}`];
|
|
381
383
|
if (run.title)
|
|
382
384
|
lines.push(` title: ${run.title}`);
|
|
385
|
+
if (run.rungroup_id)
|
|
386
|
+
lines.push(` rungroup: ${run.rungroup_id}`);
|
|
383
387
|
if (run.launched_at)
|
|
384
388
|
lines.push(` started at: ${run.launched_at}`);
|
|
385
389
|
if (run.finished_at)
|
|
@@ -400,6 +404,41 @@ function formatFetchRunsOutput(body, format) {
|
|
|
400
404
|
})
|
|
401
405
|
.join('\n');
|
|
402
406
|
}
|
|
407
|
+
// filterParam keys of the runs page which are API params on their own; the rest go under filter[...]
|
|
408
|
+
const RUNS_PAGE_PARAMS = ['search', 'tql', 'groupId'];
|
|
409
|
+
/**
|
|
410
|
+
* Parse a Testomat.io app URL of a run (/projects/:project/runs/:id)
|
|
411
|
+
* or of a runs page (/projects/:project/runs?filterParam=...) into API v2 request parts.
|
|
412
|
+
*
|
|
413
|
+
* @param {string} pageUrl
|
|
414
|
+
* @returns {{baseUrl: string, project: string, runId?: string, params: string[][]}|null}
|
|
415
|
+
*/
|
|
416
|
+
function parseRunsUrl(pageUrl) {
|
|
417
|
+
let url;
|
|
418
|
+
try {
|
|
419
|
+
url = new URL(pageUrl);
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const match = url.pathname.match(/\/projects\/([^/]+)\/runs(?:\/([^/]+))?\/?$/);
|
|
425
|
+
if (!match)
|
|
426
|
+
return null;
|
|
427
|
+
const [, project, runId] = match;
|
|
428
|
+
const params = [];
|
|
429
|
+
for (const [key, value] of new URLSearchParams(url.searchParams.get('filterParam') || '')) {
|
|
430
|
+
// "groups" only switches the page to the grouped view
|
|
431
|
+
if (!value || key === 'groups')
|
|
432
|
+
continue;
|
|
433
|
+
if (RUNS_PAGE_PARAMS.includes(key)) {
|
|
434
|
+
params.push([key, value]);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const [name, ...nested] = key.split('[');
|
|
438
|
+
params.push([`filter[${name}]${nested.map(part => `[${part}`).join('')}`, value]);
|
|
439
|
+
}
|
|
440
|
+
return { baseUrl: url.origin, project, runId, params };
|
|
441
|
+
}
|
|
403
442
|
|
|
404
443
|
module.exports.updateFilterType = updateFilterType;
|
|
405
444
|
|
|
@@ -431,6 +470,8 @@ module.exports.formatRunOutput = formatRunOutput;
|
|
|
431
470
|
|
|
432
471
|
module.exports.formatFetchRunsOutput = formatFetchRunsOutput;
|
|
433
472
|
|
|
473
|
+
module.exports.parseRunsUrl = parseRunsUrl;
|
|
474
|
+
|
|
434
475
|
module.exports.getObjectSize = getObjectSize;
|
|
435
476
|
|
|
436
477
|
module.exports.splitTestsIntoChunks = splitTestsIntoChunks;
|
package/lib/xmlReader.js
CHANGED
|
@@ -432,6 +432,8 @@ class XmlReader {
|
|
|
432
432
|
}
|
|
433
433
|
this.adapter.formatTest(t);
|
|
434
434
|
t.title = (0, utils_js_1.humanize)(t.title);
|
|
435
|
+
// meta must always include reporter version and adapter used
|
|
436
|
+
t.meta = { ...t.meta, reporterVersion: this.version, adapter: 'xml' };
|
|
435
437
|
});
|
|
436
438
|
}
|
|
437
439
|
formatErrors() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"test:playwright": "mocha tests/adapter/playwright.test.js",
|
|
66
66
|
"test:codecept": "mocha tests/adapter/codecept.test.js tests/adapter/codecept_comprehensive.test.js tests/adapter/codecept_steps_sections.test.js",
|
|
67
67
|
"test:vitest": "mocha tests/adapter/vitest.test.js",
|
|
68
|
-
"test:
|
|
68
|
+
"test:e2e": "mocha tests/adapter/e2e.test.js",
|
|
69
|
+
"test:frameworks": "npm run test:playwright && npm run test:codecept && npm run test:vitest && npm run test:e2e",
|
|
69
70
|
"test:all": "npm run test && npm run test:frameworks",
|
|
70
71
|
"test:adapters": "mocha tests/adapter/*.test.js",
|
|
71
72
|
"test:codecept:bug948": "mocha tests/adapter/codecept_aftersuite_failure.test.js",
|
|
@@ -129,6 +130,10 @@
|
|
|
129
130
|
"./lib/adapter/cypress-plugin": "./lib/adapter/cypress-plugin/index.js",
|
|
130
131
|
"./cypress-plugin": "./lib/adapter/cypress-plugin/index.js",
|
|
131
132
|
"./cypress": "./lib/adapter/cypress-plugin/index.js",
|
|
133
|
+
"./e2e": {
|
|
134
|
+
"import": "./src/adapter/e2e.js",
|
|
135
|
+
"require": "./lib/adapter/e2e.js"
|
|
136
|
+
},
|
|
132
137
|
"./lib/adapter/jasmine.js": "./lib/adapter/jasmine.js",
|
|
133
138
|
"./jasmine": "./lib/adapter/jasmine.js",
|
|
134
139
|
"./lib/adapter/jest.js": "./lib/adapter/jest.js",
|
package/src/adapter/codecept.js
CHANGED
|
@@ -11,6 +11,8 @@ import codeceptjs from 'codeceptjs';
|
|
|
11
11
|
import { log } from '../utils/log.js';
|
|
12
12
|
|
|
13
13
|
const debug = createDebugMessages('@testomatio/reporter:adapter:codeceptjs');
|
|
14
|
+
|
|
15
|
+
global.TESTOMATIO_ADAPTER = 'codeceptjs';
|
|
14
16
|
// @ts-ignore
|
|
15
17
|
if (!global.codeceptjs) {
|
|
16
18
|
// @ts-ignore
|
|
@@ -8,6 +8,8 @@ import { getTestomatIdFromTestTitle, fileSystem } from '../../utils/utils.js';
|
|
|
8
8
|
import { config } from '../../config.js';
|
|
9
9
|
import { services } from '../../services/index.js';
|
|
10
10
|
|
|
11
|
+
global.TESTOMATIO_ADAPTER = 'cucumber';
|
|
12
|
+
|
|
11
13
|
const { GherkinDocumentParser, PickleParser } = formatterHelpers;
|
|
12
14
|
const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
|
|
13
15
|
const { getPickleStepMap } = PickleParser;
|
|
@@ -3,6 +3,8 @@ import { getTestomatIdFromTestTitle, parseSuite } from '../../utils/utils.js';
|
|
|
3
3
|
import TestomatClient from '../../client.js';
|
|
4
4
|
import { config } from '../../config.js';
|
|
5
5
|
|
|
6
|
+
global.TESTOMATIO_ADAPTER = 'cypress';
|
|
7
|
+
|
|
6
8
|
const testomatioReporter = on => {
|
|
7
9
|
if (!config.TESTOMATIO) {
|
|
8
10
|
console.log('TESTOMATIO key is empty, ignoring reports');
|