@testomatio/reporter 2.7.0 → 2.7.2-beta.1
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/README.md +2 -1
- package/lib/adapter/codecept.js +81 -26
- package/lib/adapter/playwright.d.ts +1 -1
- package/lib/adapter/playwright.js +54 -34
- package/lib/adapter/utils/step-formatter.d.ts +134 -0
- package/lib/adapter/utils/step-formatter.js +237 -0
- package/lib/bin/cli.js +28 -31
- package/lib/bin/reportXml.js +5 -6
- package/lib/bin/startTest.js +2 -1
- package/lib/bin/uploadArtifacts.js +6 -6
- package/lib/client.d.ts +8 -0
- package/lib/client.js +71 -10
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +7 -1
- package/lib/pipe/bitbucket.js +2 -1
- package/lib/pipe/coverage.js +16 -15
- package/lib/pipe/debug.js +3 -3
- package/lib/pipe/github.js +3 -2
- package/lib/pipe/gitlab.js +2 -1
- package/lib/pipe/index.js +5 -5
- package/lib/pipe/testomatio.js +21 -24
- package/lib/uploader.js +3 -2
- package/lib/utils/log.d.ts +45 -0
- package/lib/utils/log.js +98 -0
- package/lib/utils/pipe_utils.js +5 -5
- package/lib/utils/utils.d.ts +10 -0
- package/lib/utils/utils.js +16 -1
- package/lib/xmlReader.js +5 -4
- package/package.json +1 -1
- package/src/adapter/codecept.js +99 -29
- package/src/adapter/playwright.js +64 -39
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/bin/cli.js +34 -31
- package/src/bin/reportXml.js +5 -6
- package/src/bin/startTest.js +3 -2
- package/src/bin/uploadArtifacts.js +6 -6
- package/src/client.js +76 -26
- package/src/constants.js +4 -0
- package/src/pipe/bitbucket.js +2 -1
- package/src/pipe/coverage.js +16 -15
- package/src/pipe/debug.js +3 -3
- package/src/pipe/github.js +4 -3
- package/src/pipe/gitlab.js +2 -1
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +32 -25
- package/src/uploader.js +3 -2
- package/src/utils/log.js +87 -0
- package/src/utils/pipe_utils.js +5 -5
- package/src/utils/utils.js +14 -0
- package/src/xmlReader.js +5 -4
- package/types/types.d.ts +3 -0
|
@@ -3,14 +3,16 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
-
import { APP_PREFIX, STATUS as Status, TESTOMAT_TMP_STORAGE_DIR } from '../constants.js';
|
|
6
|
+
import { APP_PREFIX, STATUS as Status, TESTOMAT_TMP_STORAGE_DIR, SCREENSHOTS_ON_STEPS } from '../constants.js';
|
|
7
7
|
import TestomatioClient from '../client.js';
|
|
8
|
-
import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
|
|
8
|
+
import { getTestomatIdFromTestTitle, fileSystem, truncate } from '../utils/utils.js';
|
|
9
9
|
import { services } from '../services/index.js';
|
|
10
10
|
import { dataStorage } from '../data-storage.js';
|
|
11
11
|
import { extensionMap } from '../utils/constants.js';
|
|
12
12
|
import pc from 'picocolors';
|
|
13
13
|
import { fetchLinksFromLogs } from './utils/playwright.js';
|
|
14
|
+
import { formatStep, addStatusToStep, addArtifactsToStep } from './utils/step-formatter.js';
|
|
15
|
+
import { log } from '../utils/log.js';
|
|
14
16
|
|
|
15
17
|
const reportTestPromises = [];
|
|
16
18
|
|
|
@@ -35,7 +37,7 @@ class PlaywrightReporter {
|
|
|
35
37
|
dataStorage.setContext(fullTestTitle);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
onTestEnd(test, result) {
|
|
40
|
+
async onTestEnd(test, result) {
|
|
39
41
|
// test.parent.project().__projectId
|
|
40
42
|
|
|
41
43
|
if (!this.client) return;
|
|
@@ -54,13 +56,26 @@ class PlaywrightReporter {
|
|
|
54
56
|
|
|
55
57
|
const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
|
|
56
58
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const rid = test.id || test.testId || uuidv4();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @type {{
|
|
63
|
+
* browser?: string,
|
|
64
|
+
* dependencies: string[],
|
|
65
|
+
* isMobile?: boolean
|
|
66
|
+
* metadata: Record<string, any>,
|
|
67
|
+
* name: string,
|
|
68
|
+
* }}
|
|
69
|
+
*/
|
|
70
|
+
const project = {
|
|
71
|
+
browser: test.parent.project().use.defaultBrowserType,
|
|
72
|
+
dependencies: test.parent.project().dependencies,
|
|
73
|
+
isMobile: test.parent.project().use.isMobile,
|
|
74
|
+
metadata: test.parent.project().metadata,
|
|
75
|
+
name: test.parent.project().name,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const steps = result.steps.map(step => appendStep(step, 0)).filter(step => step !== null);
|
|
64
79
|
|
|
65
80
|
// Extract and normalize tags
|
|
66
81
|
const tags = extractTags(test);
|
|
@@ -86,24 +101,6 @@ class PlaywrightReporter {
|
|
|
86
101
|
*/
|
|
87
102
|
const manuallyAttachedArtifacts = services.artifacts.get(fullTestTitle);
|
|
88
103
|
const testMeta = services.keyValues.get(fullTestTitle);
|
|
89
|
-
const rid = test.id || test.testId || uuidv4();
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @type {{
|
|
93
|
-
* browser?: string,
|
|
94
|
-
* dependencies: string[],
|
|
95
|
-
* isMobile?: boolean
|
|
96
|
-
* metadata: Record<string, any>,
|
|
97
|
-
* name: string,
|
|
98
|
-
* }}
|
|
99
|
-
*/
|
|
100
|
-
const project = {
|
|
101
|
-
browser: test.parent.project().use.defaultBrowserType,
|
|
102
|
-
dependencies: test.parent.project().dependencies,
|
|
103
|
-
isMobile: test.parent.project().use.isMobile,
|
|
104
|
-
metadata: test.parent.project().metadata,
|
|
105
|
-
name: test.parent.project().name,
|
|
106
|
-
};
|
|
107
104
|
|
|
108
105
|
let status = result.status;
|
|
109
106
|
// process test.fail() annotation
|
|
@@ -179,7 +176,7 @@ class PlaywrightReporter {
|
|
|
179
176
|
await Promise.all(reportTestPromises);
|
|
180
177
|
|
|
181
178
|
if (this.uploads.length) {
|
|
182
|
-
if (this.client.uploader.isEnabled)
|
|
179
|
+
if (this.client.uploader.isEnabled) log.info(`🎞️ Uploading ${this.uploads.length} files...`);
|
|
183
180
|
|
|
184
181
|
const promises = [];
|
|
185
182
|
|
|
@@ -242,6 +239,44 @@ function appendStep(step, shift = 0) {
|
|
|
242
239
|
newCategory = 'framework';
|
|
243
240
|
}
|
|
244
241
|
|
|
242
|
+
const resultStep = formatStep({
|
|
243
|
+
category: newCategory,
|
|
244
|
+
title: step.title,
|
|
245
|
+
duration: step.duration,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Add status based on error
|
|
249
|
+
addStatusToStep(resultStep, step.error ? 'failed' : 'passed', step.error);
|
|
250
|
+
|
|
251
|
+
// Add error if present
|
|
252
|
+
if (step.error !== undefined) {
|
|
253
|
+
if (typeof step.error === 'object') {
|
|
254
|
+
resultStep.error = {
|
|
255
|
+
message: truncate(String(step.error.message), 250),
|
|
256
|
+
stack: truncate(String(step.error.stack || ''), 250),
|
|
257
|
+
};
|
|
258
|
+
} else {
|
|
259
|
+
resultStep.error = truncate(String(step.error), 250);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Add log if present
|
|
264
|
+
if (step.log) {
|
|
265
|
+
resultStep.log = truncate(String(step.log), 250);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Add artifacts from attachments
|
|
269
|
+
if (step.attachments && step.attachments.length > 0 && SCREENSHOTS_ON_STEPS) {
|
|
270
|
+
const screenshotAttachment = step.attachments.find(att =>
|
|
271
|
+
att.contentType === 'image/png' && att.name === 'screenshot'
|
|
272
|
+
);
|
|
273
|
+
if (screenshotAttachment && screenshotAttachment.path) {
|
|
274
|
+
const artifacts = { screenshot: screenshotAttachment.path };
|
|
275
|
+
addArtifactsToStep(resultStep, artifacts);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Process nested steps
|
|
245
280
|
const formattedSteps = [];
|
|
246
281
|
for (const child of step.steps || []) {
|
|
247
282
|
const appendedChild = appendStep(child, shift + 2);
|
|
@@ -250,20 +285,10 @@ function appendStep(step, shift = 0) {
|
|
|
250
285
|
}
|
|
251
286
|
}
|
|
252
287
|
|
|
253
|
-
const resultStep = {
|
|
254
|
-
category: newCategory,
|
|
255
|
-
title: step.title,
|
|
256
|
-
duration: step.duration,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
288
|
if (formattedSteps.length) {
|
|
260
289
|
resultStep.steps = formattedSteps.filter(s => !!s);
|
|
261
290
|
}
|
|
262
291
|
|
|
263
|
-
if (step.error !== undefined) {
|
|
264
|
-
resultStep.error = step.error;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
292
|
return resultStep;
|
|
268
293
|
}
|
|
269
294
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { truncate } from '../../utils/utils.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generates a short unique filename from screenshot path
|
|
8
|
+
* If original filename is too long, uses hash-based name
|
|
9
|
+
*
|
|
10
|
+
* @param {string} screenshotPath - Path to screenshot file
|
|
11
|
+
* @returns {string} Short filename (max 80 chars)
|
|
12
|
+
*/
|
|
13
|
+
export function generateShortFilename(screenshotPath) {
|
|
14
|
+
const originalFilename = path.basename(screenshotPath);
|
|
15
|
+
const stepPrefix = originalFilename.match(/^(\d{3,4}_)/)?.[1] || '';
|
|
16
|
+
|
|
17
|
+
if (originalFilename.length < 40) {
|
|
18
|
+
return originalFilename;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ext = path.extname(screenshotPath);
|
|
22
|
+
|
|
23
|
+
const hash = crypto
|
|
24
|
+
.createHash('sha256')
|
|
25
|
+
.update(screenshotPath)
|
|
26
|
+
.digest('hex')
|
|
27
|
+
.slice(0, 16);
|
|
28
|
+
|
|
29
|
+
return `${stepPrefix}screenshot_${hash}${ext}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Formats a step object according to Testomat.io Step Schema
|
|
34
|
+
*
|
|
35
|
+
* This function transforms a raw step object from test frameworks (CodeceptJS, Playwright, etc.)
|
|
36
|
+
* into a standardized format compatible with Testomat.io API. It ensures all text fields are
|
|
37
|
+
* truncated to 250 characters as defined in testomat-api-definition.yml.
|
|
38
|
+
*
|
|
39
|
+
* Processed fields:
|
|
40
|
+
* - category: step type (framework, user, hook) - defaults to 'user'
|
|
41
|
+
* - title: step name/description, truncated to 250 chars
|
|
42
|
+
* - duration: step execution time in seconds
|
|
43
|
+
* - log: optional log output, truncated to 250 chars
|
|
44
|
+
* - artifacts: optional array of artifact URLs (screenshots), each truncated to 250 chars
|
|
45
|
+
* - error: error details (message + stack) if step failed, each truncated to 250 chars
|
|
46
|
+
* - steps: recursively formats nested steps
|
|
47
|
+
*
|
|
48
|
+
* Schema reference: testomat-api-definition.yml (Step object)
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} step - Raw step object from test framework
|
|
51
|
+
* @param {string} [step.category] - Step category: 'user', 'framework', or 'hook'
|
|
52
|
+
* @param {string} [step.title] - Step title/name
|
|
53
|
+
* @param {number} [step.duration] - Step duration in seconds
|
|
54
|
+
* @param {string} [step.log] - Log output for this step
|
|
55
|
+
* @param {string[]} [step.artifacts] - Array of artifact URLs (screenshots)
|
|
56
|
+
* @param {string|Object} [step.error] - Error details - can be string or object with message/stack
|
|
57
|
+
* @param {Object[]} [step.steps] - Array of nested child steps
|
|
58
|
+
* @returns {Object} Formatted step object matching Testomat.io Step Schema with:
|
|
59
|
+
* category, title, duration, and optional log, artifacts, error, and steps fields
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const rawStep = {
|
|
63
|
+
* category: 'user',
|
|
64
|
+
* title: 'I click on button',
|
|
65
|
+
* duration: 1.5,
|
|
66
|
+
* error: { message: 'Element not found', stack: 'at test.js:10:5' }
|
|
67
|
+
* };
|
|
68
|
+
* const formatted = formatStep(rawStep);
|
|
69
|
+
* // Returns: { category: 'user', title: 'I click on button', duration: 1.5, error: {...} }
|
|
70
|
+
*/
|
|
71
|
+
export function formatStep(step) {
|
|
72
|
+
const formattedStep = {
|
|
73
|
+
category: step.category || 'user',
|
|
74
|
+
title: truncate(String(step.title || ''), 250),
|
|
75
|
+
duration: step.duration || 0,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (step.log) {
|
|
79
|
+
formattedStep.log = truncate(String(step.log), 250);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
83
|
+
formattedStep.artifacts = step.artifacts.map(artifact => truncate(String(artifact), 250));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (step.error) {
|
|
87
|
+
if (typeof step.error === 'object') {
|
|
88
|
+
formattedStep.error = {
|
|
89
|
+
message: truncate(String(step.error.message || 'Step failed'), 250),
|
|
90
|
+
stack: truncate(String(step.error.stack || ''), 250),
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
formattedStep.error = truncate(String(step.error), 250);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (step.steps && Array.isArray(step.steps)) {
|
|
98
|
+
formattedStep.steps = step.steps.map(s => formatStep(s));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return formattedStep;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Adds status field to step
|
|
106
|
+
*
|
|
107
|
+
* Normalizes step status from test frameworks to Testomat.io standard format.
|
|
108
|
+
* Maps framework-specific statuses ('success', 'failed', 'passed') to Testomat.io
|
|
109
|
+
* standard values ('passed', 'failed').
|
|
110
|
+
*
|
|
111
|
+
* Status mapping:
|
|
112
|
+
* - 'success' → 'passed'
|
|
113
|
+
* - 'passed' → 'passed'
|
|
114
|
+
* - 'failed' → 'failed'
|
|
115
|
+
* - Any other value → 'passed' (default)
|
|
116
|
+
*
|
|
117
|
+
* If step already has a status, it won't be overwritten. If error is provided
|
|
118
|
+
* and step doesn't have status, it will be set to 'failed'.
|
|
119
|
+
*
|
|
120
|
+
* Schema reference: testomat-api-definition.yml (Step.status enum)
|
|
121
|
+
*
|
|
122
|
+
* @param {Object} step - Step object to add status to (modified in place)
|
|
123
|
+
* @param {string} [step.status] - Existing status (won't be overwritten if present)
|
|
124
|
+
* @param {string} status - Status from test framework: 'success', 'failed', or 'passed'
|
|
125
|
+
* @param {Error|Object|null} err - Error object if step failed
|
|
126
|
+
* @returns {Object} The same step object with added status field
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const step = { title: 'Click button' };
|
|
130
|
+
* addStatusToStep(step, 'success', null);
|
|
131
|
+
* // step.status === 'passed'
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* const step2 = { title: 'Find element' };
|
|
135
|
+
* addStatusToStep(step2, 'failed', new Error('Not found'));
|
|
136
|
+
* // step2.status === 'failed'
|
|
137
|
+
*/
|
|
138
|
+
export function addStatusToStep(step, status, err) {
|
|
139
|
+
if (step.status) return step;
|
|
140
|
+
|
|
141
|
+
const statusMap = {
|
|
142
|
+
'success': 'passed',
|
|
143
|
+
'failed': 'failed',
|
|
144
|
+
'passed': 'passed',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
step.status = statusMap[status] || 'passed';
|
|
148
|
+
|
|
149
|
+
if (err && !step.status) {
|
|
150
|
+
step.status = 'failed';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return step;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Adds screenshot to step as artifacts array
|
|
158
|
+
*
|
|
159
|
+
* Extracts screenshot path from artifacts and adds it to the step's artifacts array.
|
|
160
|
+
* The actual upload will happen in the client's addTestRun method.
|
|
161
|
+
*
|
|
162
|
+
* Artifact format supports:
|
|
163
|
+
* - Array format: [{ screenshot: '/path/to/screenshot.png' }]
|
|
164
|
+
* - Object format: { screenshot: '/path/to/screenshot.png' }
|
|
165
|
+
*
|
|
166
|
+
* Screenshot path can be specified as:
|
|
167
|
+
* - Object with path property: { screenshot: { path: '/path/to/file.png' } }
|
|
168
|
+
* - Object with screenshot property: { screenshot: { screenshot: '/path/to/file.png' } }
|
|
169
|
+
* - Direct string path: { screenshot: '/path/to/file.png' }
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} step - Step object to add artifacts to (modified in place)
|
|
172
|
+
* @param {string[]} [step.artifacts] - Existing artifacts array (won't be overwritten if present)
|
|
173
|
+
* @param {Object|Object[]|null} artifacts - Artifacts from test framework
|
|
174
|
+
* @returns {Object} The same step object with artifacts array added
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const step = { title: 'Click button' };
|
|
178
|
+
* const artifacts = { screenshot: '/tmp/screenshot.png' };
|
|
179
|
+
* addArtifactsToStep(step, artifacts);
|
|
180
|
+
* // step.artifacts === ['/tmp/screenshot.png']
|
|
181
|
+
*/
|
|
182
|
+
export function addArtifactsToStep(step, artifacts) {
|
|
183
|
+
if (!artifacts) return step;
|
|
184
|
+
|
|
185
|
+
let screenshotPath = null;
|
|
186
|
+
|
|
187
|
+
if (Array.isArray(artifacts)) {
|
|
188
|
+
const screenshotArtifact = artifacts.find(a => a.screenshot);
|
|
189
|
+
if (screenshotArtifact && screenshotArtifact.path) {
|
|
190
|
+
screenshotPath = screenshotArtifact.path;
|
|
191
|
+
} else if (screenshotArtifact && screenshotArtifact.screenshot) {
|
|
192
|
+
screenshotPath = screenshotArtifact.screenshot;
|
|
193
|
+
}
|
|
194
|
+
} else if (artifacts.screenshot) {
|
|
195
|
+
screenshotPath = artifacts.screenshot;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (screenshotPath && fs.existsSync(screenshotPath)) {
|
|
199
|
+
const truncatedPath = truncate(String(screenshotPath), 250);
|
|
200
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
201
|
+
step.artifacts.push(truncatedPath);
|
|
202
|
+
} else {
|
|
203
|
+
step.artifacts = [truncatedPath];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return step;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Appends one artifact path to a step.
|
|
212
|
+
*
|
|
213
|
+
* Unlike addArtifactsToStep, this helper accepts a direct path (or URL-like string)
|
|
214
|
+
* and does not check file existence, so callers can attach fallback artifacts
|
|
215
|
+
* collected from logs or async trace outputs.
|
|
216
|
+
*
|
|
217
|
+
* @param {Object} step - Step object to update (modified in place)
|
|
218
|
+
* @param {string} artifactPath - Artifact path to append
|
|
219
|
+
* @returns {Object} The same step object with updated artifacts
|
|
220
|
+
*/
|
|
221
|
+
export function addArtifactPathToStep(step, artifactPath) {
|
|
222
|
+
if (!step || !artifactPath) return step;
|
|
223
|
+
|
|
224
|
+
const truncatedPath = truncate(String(artifactPath), 250);
|
|
225
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
226
|
+
if (!step.artifacts.includes(truncatedPath)) step.artifacts.push(truncatedPath);
|
|
227
|
+
} else {
|
|
228
|
+
step.artifacts = [truncatedPath];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return step;
|
|
232
|
+
}
|
package/src/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import pc from 'picocolors';
|
|
|
14
14
|
import { filesize as prettyBytes } from 'filesize';
|
|
15
15
|
import dotenv from 'dotenv';
|
|
16
16
|
import Replay from '../replay.js';
|
|
17
|
+
import { log } from '../utils/log.js';
|
|
17
18
|
|
|
18
19
|
const debug = createDebugMessages('@testomatio/reporter:cli');
|
|
19
20
|
const version = getPackageVersion();
|
|
@@ -89,7 +90,7 @@ program
|
|
|
89
90
|
const client = new TestomatClient({ apiKey, title });
|
|
90
91
|
|
|
91
92
|
if (opts.filter || opts.filterList) {
|
|
92
|
-
|
|
93
|
+
log.info('Filtering tests...');
|
|
93
94
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter "testomatio:tag-name=frontend"
|
|
94
95
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter "coverage:file=coverage.yml"
|
|
95
96
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter-list "coverage:file=coverage.yml"
|
|
@@ -105,7 +106,7 @@ program
|
|
|
105
106
|
const tests = await client.prepareRun(prepareRunParams);
|
|
106
107
|
|
|
107
108
|
if (!tests || tests.length === 0) {
|
|
108
|
-
|
|
109
|
+
log.info( pc.yellow('No tests found.'));
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -115,8 +116,8 @@ program
|
|
|
115
116
|
debug(`Execution pattern: "${pattern}"`);
|
|
116
117
|
|
|
117
118
|
if(opts.filterList) {
|
|
118
|
-
|
|
119
|
-
if (command)
|
|
119
|
+
log.info( pc.blue(`Matched test/suite IDs: ${tests.join(', ')}`));
|
|
120
|
+
if (command) log.info( pc.green(`Full Running Command: ${filteredCommand}`));
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -125,7 +126,7 @@ program
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
catch (err) {
|
|
128
|
-
|
|
129
|
+
log.info( err.message || err);
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
@@ -143,20 +144,20 @@ program
|
|
|
143
144
|
if (apiKey) {
|
|
144
145
|
await client.createRun(createRunParams);
|
|
145
146
|
const runId = process.env.TESTOMATIO_RUN || process.env.runId;
|
|
146
|
-
if (client.pipeStore.runUrl)
|
|
147
|
+
if (client.pipeStore.runUrl) log.info( `📊 Report URL: ${pc.magenta(client.pipeStore.runUrl)}`);
|
|
147
148
|
|
|
148
149
|
if (opts.kind !== 'manual') {
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
log.info( `No command passed, so you need to run tests yourself:`);
|
|
151
|
+
log.info( `TESTOMATIO_RUN=${runId} <command>`);
|
|
151
152
|
}
|
|
152
153
|
} else {
|
|
153
|
-
|
|
154
|
+
log.info( '⚠️ No API key provided. Cannot create run without TESTOMATIO key.');
|
|
154
155
|
process.exit(1);
|
|
155
156
|
}
|
|
156
157
|
return process.exit(0);
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
log.info( `🚀 Running`, pc.green(command));
|
|
160
161
|
|
|
161
162
|
const runTests = async () => {
|
|
162
163
|
const testCmds = command.split(' ');
|
|
@@ -167,7 +168,7 @@ program
|
|
|
167
168
|
|
|
168
169
|
cmd.on('close', async code => {
|
|
169
170
|
const emoji = code === 0 ? '🟢' : '🔴';
|
|
170
|
-
|
|
171
|
+
log.info( emoji, `Runner exited with ${pc.bold(code)}`);
|
|
171
172
|
if (apiKey) {
|
|
172
173
|
const status = code === 0 ? 'passed' : 'failed';
|
|
173
174
|
await client.updateRunStatus(status);
|
|
@@ -209,7 +210,7 @@ program
|
|
|
209
210
|
// const runReader = new XmlReader({ javaTests, lang });
|
|
210
211
|
// const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
211
212
|
// if (!files.length) {
|
|
212
|
-
//
|
|
213
|
+
// log.info( `Report can't be created. No XML files found 😥`);
|
|
213
214
|
// process.exit(1);
|
|
214
215
|
// }
|
|
215
216
|
|
|
@@ -231,12 +232,12 @@ program
|
|
|
231
232
|
const runReader = new XmlReader({ javaTests, lang });
|
|
232
233
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
233
234
|
if (!files.length) {
|
|
234
|
-
|
|
235
|
+
log.info( `Report can't be created. No XML files found 😥`);
|
|
235
236
|
process.exit(1);
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
for (const file of files) {
|
|
239
|
-
|
|
240
|
+
log.info( `Parsed ${file}`);
|
|
240
241
|
runReader.parse(file);
|
|
241
242
|
}
|
|
242
243
|
|
|
@@ -257,7 +258,7 @@ program
|
|
|
257
258
|
await runReader.createRun();
|
|
258
259
|
await runReader.uploadData();
|
|
259
260
|
} catch (err) {
|
|
260
|
-
|
|
261
|
+
log.info( 'Error updating status, skipping...', err);
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
@@ -292,10 +293,10 @@ program
|
|
|
292
293
|
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
293
294
|
|
|
294
295
|
if (!testruns.length) {
|
|
295
|
-
|
|
296
|
+
log.info( '🗄️ Total artifacts:', numTotalArtifacts);
|
|
296
297
|
if (numTotalArtifacts) {
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
log.info( 'No new artifacts to upload');
|
|
299
|
+
log.info( 'To re-upload artifacts run this command with --force flag');
|
|
299
300
|
}
|
|
300
301
|
process.exit(0);
|
|
301
302
|
}
|
|
@@ -317,7 +318,7 @@ program
|
|
|
317
318
|
await client.addTestRun(undefined, { rid, files });
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
|
|
321
|
+
log.info( '🗄️', client.uploader.successfulUploads.length, 'artifacts 🟢uploaded');
|
|
321
322
|
|
|
322
323
|
if (client.uploader.successfulUploads.length) {
|
|
323
324
|
debug('\n', APP_PREFIX, `🗄️ ${client.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
|
|
@@ -376,11 +377,11 @@ program
|
|
|
376
377
|
const replayService = new Replay({
|
|
377
378
|
apiKey: config.TESTOMATIO,
|
|
378
379
|
dryRun: opts.dryRun,
|
|
379
|
-
onLog: message =>
|
|
380
|
-
onError: message =>
|
|
380
|
+
onLog: message => log.info( message),
|
|
381
|
+
onError: message => log.error( '⚠️ ', message),
|
|
381
382
|
onProgress: ({ current, total }) => {
|
|
382
383
|
if (current % 10 === 0 || current === total) {
|
|
383
|
-
|
|
384
|
+
log.info( `📊 Progress: ${current}/${total} tests processed`);
|
|
384
385
|
}
|
|
385
386
|
},
|
|
386
387
|
});
|
|
@@ -388,23 +389,25 @@ program
|
|
|
388
389
|
const result = await replayService.replay(debugFile);
|
|
389
390
|
|
|
390
391
|
if (result.dryRun) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
392
|
+
log.info(
|
|
393
|
+
'🔍 Dry run completed:\n',
|
|
394
|
+
` - Tests found: ${result.testsCount}\n`,
|
|
395
|
+
` - Environment variables: ${Object.keys(result.envVars).length}\n`,
|
|
396
|
+
' - Run parameters:', result.runParams, '\n',
|
|
397
|
+
' Use without --dry-run to actually send the data',
|
|
398
|
+
);
|
|
396
399
|
} else {
|
|
397
|
-
|
|
400
|
+
log.info( `✅ Successfully replayed ${result.successCount}/${result.testsCount} tests`);
|
|
398
401
|
if (result.failureCount > 0) {
|
|
399
|
-
|
|
402
|
+
log.info( `⚠️ ${result.failureCount} tests failed to upload`);
|
|
400
403
|
}
|
|
401
404
|
}
|
|
402
405
|
|
|
403
406
|
process.exit(0);
|
|
404
407
|
} catch (err) {
|
|
405
|
-
|
|
408
|
+
log.error( '❌ Error replaying debug data:', err.message);
|
|
406
409
|
if (err.message.includes('Debug file not found')) {
|
|
407
|
-
|
|
410
|
+
log.error( '💡 Hint: Run tests with TESTOMATIO_DEBUG=1 to generate debug files');
|
|
408
411
|
}
|
|
409
412
|
process.exit(1);
|
|
410
413
|
}
|
package/src/bin/reportXml.js
CHANGED
|
@@ -3,11 +3,10 @@ import { Command } from 'commander';
|
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import createDebugMessages from 'debug';
|
|
6
|
-
import { APP_PREFIX } from '../constants.js';
|
|
7
6
|
import XmlReader from '../xmlReader.js';
|
|
8
7
|
import { getPackageVersion } from '../utils/utils.js';
|
|
9
8
|
import dotenv from 'dotenv';
|
|
10
|
-
import
|
|
9
|
+
import { log } from '../utils/log.js';
|
|
11
10
|
|
|
12
11
|
const version = getPackageVersion();
|
|
13
12
|
|
|
@@ -28,7 +27,7 @@ program
|
|
|
28
27
|
}
|
|
29
28
|
let { javaTests, lang } = opts;
|
|
30
29
|
if (opts.envFile) {
|
|
31
|
-
|
|
30
|
+
log.info( 'Loading env file:', opts.envFile);
|
|
32
31
|
debug('Loading env file: %s', opts.envFile);
|
|
33
32
|
dotenv.config({ path: opts.envFile });
|
|
34
33
|
}
|
|
@@ -40,13 +39,13 @@ program
|
|
|
40
39
|
});
|
|
41
40
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
42
41
|
if (!files.length) {
|
|
43
|
-
|
|
42
|
+
log.info( `Report can't be created. No XML files found 😥`);
|
|
44
43
|
process.exitCode = 1;
|
|
45
44
|
return;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
for (const file of files) {
|
|
49
|
-
|
|
48
|
+
log.info( `Parsed ${file}`);
|
|
50
49
|
runReader.parse(file);
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -67,7 +66,7 @@ program
|
|
|
67
66
|
await runReader.createRun();
|
|
68
67
|
await runReader.uploadData();
|
|
69
68
|
} catch (err) {
|
|
70
|
-
|
|
69
|
+
log.info( 'Error updating status, skipping...', err);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
package/src/bin/startTest.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { getPackageVersion } from '../utils/utils.js';
|
|
5
6
|
import pc from 'picocolors';
|
|
6
7
|
|
|
7
|
-
//
|
|
8
|
-
const __dirname = typeof
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
const __dirname = typeof global.__dirname !== 'undefined' ? global.__dirname : dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const cliPath = join(__dirname, 'cli.js');
|
|
10
11
|
|
|
11
12
|
const version = getPackageVersion();
|
|
@@ -4,11 +4,11 @@ import { Command } from 'commander';
|
|
|
4
4
|
import pc from 'picocolors';
|
|
5
5
|
import createDebugMessages from 'debug';
|
|
6
6
|
import TestomatClient from '../client.js';
|
|
7
|
-
import { APP_PREFIX } from '../constants.js';
|
|
8
7
|
import { getPackageVersion } from '../utils/utils.js';
|
|
9
8
|
import { config } from '../config.js';
|
|
10
9
|
import { readLatestRunId } from '../utils/utils.js';
|
|
11
10
|
import dotenv from 'dotenv';
|
|
11
|
+
import { log } from '../utils/log.js';
|
|
12
12
|
|
|
13
13
|
const debug = createDebugMessages('@testomatio/reporter:upload-cli');
|
|
14
14
|
const version = getPackageVersion();
|
|
@@ -48,10 +48,10 @@ program
|
|
|
48
48
|
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
49
49
|
|
|
50
50
|
if (!testruns.length) {
|
|
51
|
-
|
|
51
|
+
log.info('Total artifacts:', numTotalArtifacts);
|
|
52
52
|
if (numTotalArtifacts) {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
log.info('No new artifacts to upload');
|
|
54
|
+
log.info('To re-upload artifacts run this command with --force flag');
|
|
55
55
|
}
|
|
56
56
|
process.exit(0);
|
|
57
57
|
}
|
|
@@ -78,9 +78,9 @@ program
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
log.info(client.uploader.successfulUploads.length, 'artifacts uploaded');
|
|
82
82
|
if (client.uploader.failedUploads.length) {
|
|
83
|
-
|
|
83
|
+
log.info(client.uploader.failedUploads.length, 'artifacts failed to upload');
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
86
|
|