@appliqation/automation-sdk 2.2.0 → 2.3.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 +269 -7
- package/package.json +2 -2
- package/src/AppliqationClient.js +47 -2
- package/src/core/HttpClient.js +52 -0
- package/src/reporters/cypress/CypressReporter.js +5 -5
- package/src/reporters/jest/JestReporter.js +7 -7
- package/src/reporters/playwright/AppliqationReporter.js +378 -72
- package/src/reporters/playwright/helpers/UuidExtractor.js +8 -3
- package/src/services/ResultService.js +36 -1
- package/src/services/RunMatrixService.js +28 -0
- package/src/services/TaggingService.js +211 -14
|
@@ -320,6 +320,34 @@ class RunMatrixService {
|
|
|
320
320
|
};
|
|
321
321
|
return normalizeOS(osMap[platform] || 'Unknown');
|
|
322
322
|
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Delete a test run
|
|
326
|
+
* @param {string} runId - Run ID to delete
|
|
327
|
+
* @param {string} reason - Deletion reason
|
|
328
|
+
* @returns {Promise<Object>} Deletion result
|
|
329
|
+
*/
|
|
330
|
+
async delete(runId, reason = 'orphan_cleanup') {
|
|
331
|
+
try {
|
|
332
|
+
logger.info('Deleting run...', { runId, reason });
|
|
333
|
+
|
|
334
|
+
const response = await this.http.deleteRun(runId, reason);
|
|
335
|
+
|
|
336
|
+
if (!response.success) {
|
|
337
|
+
throw new Error(response.error || 'Failed to delete run');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
logger.info('Run deleted successfully', { runId });
|
|
341
|
+
|
|
342
|
+
return response.data || response;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
logger.error('Failed to delete run', {
|
|
345
|
+
error: error.message,
|
|
346
|
+
runId
|
|
347
|
+
});
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
323
351
|
}
|
|
324
352
|
|
|
325
353
|
module.exports = RunMatrixService;
|
|
@@ -1,43 +1,240 @@
|
|
|
1
1
|
const logger = require('../utils/logger');
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Service for auto-tagging test cases after successful runs
|
|
5
|
+
*/
|
|
3
6
|
class TaggingService {
|
|
4
7
|
constructor(httpClient, config = { options: {} }) {
|
|
5
8
|
this.http = httpClient;
|
|
6
9
|
this.config = config;
|
|
10
|
+
|
|
11
|
+
// Configuration resolution: Runtime options > Env vars > Defaults
|
|
12
|
+
this.enabled = this.config?.options?.autoTag !== false &&
|
|
13
|
+
process.env.APPLIQATION_AUTO_TAG_ENABLED !== 'false';
|
|
14
|
+
this.tagName = this.config?.options?.autoTagName ||
|
|
15
|
+
process.env.APPLIQATION_AUTO_TAG_NAME ||
|
|
16
|
+
'Appq_automated';
|
|
17
|
+
this.batchSize = this.config?.options?.autoTagBatchSize || 50;
|
|
18
|
+
this.retries = this.config?.options?.autoTagRetries || 2;
|
|
7
19
|
}
|
|
8
20
|
|
|
9
21
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @param {string[]} uuids
|
|
13
|
-
* @param {string} tagName
|
|
22
|
+
* Check if tagging is enabled
|
|
23
|
+
* @returns {boolean}
|
|
14
24
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
isEnabled() {
|
|
26
|
+
return this.enabled;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Check which UUIDs already have the tag
|
|
31
|
+
*
|
|
32
|
+
* @param {string[]} uuids - Array of test case UUIDs
|
|
33
|
+
* @param {string} tagName - Tag name (optional, uses config default)
|
|
34
|
+
* @returns {Promise<Object>} { needsTagging: [], alreadyTagged: [], errors: [] }
|
|
35
|
+
*/
|
|
36
|
+
async checkTagStatus(uuids, tagName = null) {
|
|
37
|
+
if (!uuids || uuids.length === 0) {
|
|
38
|
+
return { needsTagging: [], alreadyTagged: [], errors: [] };
|
|
20
39
|
}
|
|
21
40
|
|
|
41
|
+
const tag = tagName || this.tagName;
|
|
22
42
|
const unique = Array.from(new Set(uuids.filter(Boolean)));
|
|
23
|
-
if (unique.length === 0) return;
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
try {
|
|
45
|
+
// Build query string: uuids=123-xxx,124-yyy&tag=Appq_automated
|
|
46
|
+
const uuidsParam = unique.join(',');
|
|
47
|
+
const url = `/api/automation/testcases/tags/check?uuids=${encodeURIComponent(uuidsParam)}&tag=${encodeURIComponent(tag)}`;
|
|
48
|
+
const response = await this.http.get(url);
|
|
49
|
+
|
|
50
|
+
if (response.success && response.data) {
|
|
51
|
+
return {
|
|
52
|
+
needsTagging: response.data.needsTagging || [],
|
|
53
|
+
alreadyTagged: response.data.alreadyTagged || [],
|
|
54
|
+
errors: response.data.errors || []
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(response.error || 'Failed to check tag status');
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error('Failed to check tag status', {
|
|
61
|
+
error: error.message,
|
|
62
|
+
count: unique.length,
|
|
63
|
+
tag
|
|
64
|
+
});
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tag test cases
|
|
71
|
+
*
|
|
72
|
+
* @param {string[]} uuids - Array of test case UUIDs to tag
|
|
73
|
+
* @param {string} tagName - Tag name (optional, uses config default)
|
|
74
|
+
* @returns {Promise<Object>} { tagged: 0, failed: 0, errors: [] }
|
|
75
|
+
*/
|
|
76
|
+
async tagTestCases(uuids, tagName = null) {
|
|
77
|
+
if (!uuids || uuids.length === 0) {
|
|
78
|
+
return { tagged: 0, failed: 0, errors: [] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const tag = tagName || this.tagName;
|
|
82
|
+
const unique = Array.from(new Set(uuids.filter(Boolean)));
|
|
27
83
|
|
|
28
84
|
try {
|
|
29
|
-
await this.http.post(
|
|
85
|
+
const response = await this.http.post('/api/automation/testcases/tag', {
|
|
30
86
|
uuids: unique,
|
|
31
87
|
tag
|
|
32
88
|
});
|
|
33
|
-
|
|
89
|
+
|
|
90
|
+
if (response.success && response.data) {
|
|
91
|
+
return {
|
|
92
|
+
tagged: response.data.tagged || 0,
|
|
93
|
+
failed: response.data.failed || 0,
|
|
94
|
+
errors: response.data.errors || []
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error(response.error || 'Failed to tag test cases');
|
|
34
99
|
} catch (error) {
|
|
35
|
-
logger.
|
|
100
|
+
logger.error('Failed to tag test cases', {
|
|
36
101
|
error: error.message,
|
|
37
102
|
count: unique.length,
|
|
38
103
|
tag
|
|
39
104
|
});
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Auto-tag accepted results (main entry point)
|
|
111
|
+
*
|
|
112
|
+
* This is a fire-and-forget operation:
|
|
113
|
+
* 1. Check which UUIDs need tagging
|
|
114
|
+
* 2. Tag only those that don't have the tag yet
|
|
115
|
+
* 3. Process in batches for performance
|
|
116
|
+
* 4. Never throws - logs warnings on failure
|
|
117
|
+
*
|
|
118
|
+
* @param {string[]} acceptedUuids - UUIDs of accepted results
|
|
119
|
+
* @param {Object} options - Optional overrides
|
|
120
|
+
* @param {string} options.tagName - Custom tag name
|
|
121
|
+
* @returns {Promise<Object>} { tagged: 0, skipped: 0, failed: 0 }
|
|
122
|
+
*/
|
|
123
|
+
async autoTagAcceptedResults(acceptedUuids = [], options = {}) {
|
|
124
|
+
// Safety checks
|
|
125
|
+
if (!this.enabled) {
|
|
126
|
+
logger.debug('Auto-tagging disabled');
|
|
127
|
+
return { tagged: 0, skipped: 0, failed: 0 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!acceptedUuids || acceptedUuids.length === 0) {
|
|
131
|
+
return { tagged: 0, skipped: 0, failed: 0 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Deduplicate and filter
|
|
135
|
+
const unique = Array.from(new Set(acceptedUuids.filter(Boolean)));
|
|
136
|
+
if (unique.length === 0) {
|
|
137
|
+
return { tagged: 0, skipped: 0, failed: 0 };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const tag = options.tagName || this.tagName;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
logger.debug('Starting auto-tag process', { count: unique.length, tag });
|
|
144
|
+
|
|
145
|
+
// Step 1: Check which UUIDs need tagging
|
|
146
|
+
const statusCheck = await this.checkTagStatus(unique, tag);
|
|
147
|
+
const needsTagging = statusCheck.needsTagging || [];
|
|
148
|
+
const alreadyTagged = statusCheck.alreadyTagged || [];
|
|
149
|
+
logger.debug('Tag status check complete', {
|
|
150
|
+
needsTagging: needsTagging.length,
|
|
151
|
+
alreadyTagged: alreadyTagged.length,
|
|
152
|
+
errors: statusCheck.errors.length
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (needsTagging.length === 0) {
|
|
156
|
+
logger.debug('No test cases need tagging (all already tagged)');
|
|
157
|
+
return {
|
|
158
|
+
tagged: 0,
|
|
159
|
+
skipped: alreadyTagged.length,
|
|
160
|
+
failed: statusCheck.errors.length
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 2: Tag test cases (in batches if needed)
|
|
165
|
+
let totalTagged = 0;
|
|
166
|
+
let totalFailed = statusCheck.errors.length;
|
|
167
|
+
|
|
168
|
+
const batches = this.chunkArray(needsTagging, this.batchSize);
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < batches.length; i++) {
|
|
171
|
+
const batch = batches[i];
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const result = await this.tagTestCases(batch, tag);
|
|
175
|
+
totalTagged += result.tagged;
|
|
176
|
+
totalFailed += result.failed;
|
|
177
|
+
|
|
178
|
+
logger.debug(`Tagged batch ${i + 1}/${batches.length}`, {
|
|
179
|
+
tagged: result.tagged,
|
|
180
|
+
failed: result.failed
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logger.warn(`Batch ${i + 1} tagging failed`, {
|
|
184
|
+
error: error.message,
|
|
185
|
+
batchSize: batch.length
|
|
186
|
+
});
|
|
187
|
+
totalFailed += batch.length;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const summary = {
|
|
192
|
+
tagged: totalTagged,
|
|
193
|
+
skipped: alreadyTagged.length,
|
|
194
|
+
failed: totalFailed
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
logger.info('Auto-tagging complete', summary);
|
|
198
|
+
|
|
199
|
+
return summary;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// Non-blocking: Log warning and continue
|
|
202
|
+
logger.warn('Auto-tagging failed (non-blocking)', {
|
|
203
|
+
error: error.message,
|
|
204
|
+
count: unique.length,
|
|
205
|
+
tag
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
tagged: 0,
|
|
210
|
+
skipped: 0,
|
|
211
|
+
failed: unique.length
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Fire-and-forget tagging for accepted UUIDs.
|
|
218
|
+
* Does not throw; logs warnings on failure.
|
|
219
|
+
*
|
|
220
|
+
* @deprecated Use autoTagAcceptedResults() instead
|
|
221
|
+
* @param {string[]} uuids
|
|
222
|
+
* @param {string} tagName
|
|
223
|
+
*/
|
|
224
|
+
async tagAccepted(uuids = [], tagName) {
|
|
225
|
+
return this.autoTagAcceptedResults(uuids, { tagName });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Split array into chunks
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
chunkArray(array, size) {
|
|
233
|
+
const chunks = [];
|
|
234
|
+
for (let i = 0; i < array.length; i += size) {
|
|
235
|
+
chunks.push(array.slice(i, i + size));
|
|
40
236
|
}
|
|
237
|
+
return chunks;
|
|
41
238
|
}
|
|
42
239
|
}
|
|
43
240
|
|