@appliqation/automation-sdk 2.1.10 ā 2.2.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/LICENSE +0 -0
- package/README.md +260 -20
- package/package.json +9 -1
- package/src/AppliqationClient.js +1 -1
- package/src/constants.js +0 -0
- package/src/core/AuthManager.js +0 -0
- package/src/core/HttpClient.js +0 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +0 -0
- package/src/playwright/JwtBrowserAuth.js +0 -0
- package/src/playwright/fixture.js +0 -0
- package/src/playwright/global-setup.js +43 -0
- package/src/playwright/global-teardown.js +42 -0
- package/src/playwright/helpers/jwt-browser-auth.js +0 -0
- package/src/playwright/index.js +0 -0
- package/src/reporters/cypress/CypressReporter.js +49 -2
- package/src/reporters/cypress/UuidExtractor.js +0 -0
- package/src/reporters/cypress/index.js +0 -0
- package/src/reporters/jest/JestReporter.js +49 -2
- package/src/reporters/jest/UuidExtractor.js +0 -0
- package/src/reporters/jest/index.js +0 -0
- package/src/reporters/playwright/AppliqationReporter.js +424 -22
- package/src/reporters/playwright/helpers/DeviceOsDetector.js +0 -0
- package/src/reporters/playwright/helpers/UuidExtractor.js +0 -0
- package/src/reporters/playwright/index.d.ts +0 -0
- package/src/reporters/playwright/index.js +0 -0
- package/src/services/OrphanTestService.js +0 -0
- package/src/services/ResultService.js +158 -24
- package/src/services/RunMatrixService.js +16 -0
- package/src/services/TaggingService.js +44 -0
- package/src/utils/PayloadBuilder.js +0 -0
- package/src/utils/RunDataNormalizer.js +0 -0
- package/src/utils/UuidValidator.js +0 -0
- package/src/utils/errors.js +0 -0
- package/src/utils/index.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/utils/mapAppqUuid.js +0 -0
- package/src/utils/validator.js +0 -0
|
@@ -2,8 +2,9 @@ const logger = require('../utils/logger');
|
|
|
2
2
|
const { normalizeBrowser } = require('../utils/RunDataNormalizer');
|
|
3
3
|
|
|
4
4
|
class ResultService {
|
|
5
|
-
constructor(httpClient) {
|
|
5
|
+
constructor(httpClient, config = { options: {} }) {
|
|
6
6
|
this.http = httpClient;
|
|
7
|
+
this.config = config;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -15,7 +16,15 @@ class ResultService {
|
|
|
15
16
|
*/
|
|
16
17
|
async submitSingle(runId, result, runMetadata = {}) {
|
|
17
18
|
try {
|
|
18
|
-
|
|
19
|
+
if (this.config?.options?.enableAppq === false) {
|
|
20
|
+
logger.info('Appq disabled - skipping single result submission', {
|
|
21
|
+
runId,
|
|
22
|
+
uuid: result.uuid
|
|
23
|
+
});
|
|
24
|
+
return { success: true, skipped: true, reason: 'Appq disabled' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const payload = this.buildAutomationResultPayload(runId, result, runMetadata);
|
|
19
28
|
|
|
20
29
|
logger.debug('Submitting single result', {
|
|
21
30
|
runId,
|
|
@@ -23,15 +32,26 @@ class ResultService {
|
|
|
23
32
|
status: result.status
|
|
24
33
|
});
|
|
25
34
|
|
|
26
|
-
// Use
|
|
27
|
-
const response = await this.http.post('/api/
|
|
35
|
+
// Use automation endpoint to enable project validation
|
|
36
|
+
const response = await this.http.post('/api/automation/result/submit', payload);
|
|
37
|
+
|
|
38
|
+
// DEBUG: Show response with context
|
|
39
|
+
console.log('\nš ========== SUBMIT RESULT RESPONSE ==========');
|
|
40
|
+
console.log('UUID:', result.uuid);
|
|
41
|
+
if (response.data && response.data.debug_context) {
|
|
42
|
+
console.log('Project ID (from API):', response.data.debug_context.project_id);
|
|
43
|
+
console.log('User ID (from API):', response.data.debug_context.uid);
|
|
44
|
+
}
|
|
45
|
+
console.log('Status:', response.status);
|
|
46
|
+
console.log('Success:', response.success);
|
|
47
|
+
console.log('===========================================\n');
|
|
28
48
|
|
|
29
49
|
// Check if request was successful
|
|
30
50
|
if (!response.success) {
|
|
31
51
|
throw new Error(response.error || 'Result submission failed');
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
return response;
|
|
54
|
+
return response.data || response;
|
|
35
55
|
} catch (error) {
|
|
36
56
|
logger.error('Failed to submit result', {
|
|
37
57
|
error: error.message,
|
|
@@ -60,6 +80,16 @@ class ResultService {
|
|
|
60
80
|
runMetadata = {}
|
|
61
81
|
} = options;
|
|
62
82
|
|
|
83
|
+
if (this.config?.options?.enableAppq === false) {
|
|
84
|
+
logger.info(`Appq disabled - skipping batch submission of ${results?.length || 0} result(s)`);
|
|
85
|
+
return {
|
|
86
|
+
success: 0,
|
|
87
|
+
failed: 0,
|
|
88
|
+
total: results?.length || 0,
|
|
89
|
+
skipped: results?.length || 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
if (!results || results.length === 0) {
|
|
64
94
|
logger.warn('No results to submit');
|
|
65
95
|
return {
|
|
@@ -71,53 +101,115 @@ class ResultService {
|
|
|
71
101
|
|
|
72
102
|
logger.info(`Starting batch submission of ${results.length} results`);
|
|
73
103
|
|
|
74
|
-
// Group results by
|
|
75
|
-
const
|
|
104
|
+
// Group results by runId for automation endpoint
|
|
105
|
+
const groupedByRun = this.groupByRun(results);
|
|
76
106
|
|
|
77
107
|
const allResults = [];
|
|
78
108
|
const failures = [];
|
|
79
109
|
|
|
80
|
-
for (const [
|
|
81
|
-
const batches = this.chunkArray(
|
|
110
|
+
for (const [runId, runResults] of Object.entries(groupedByRun)) {
|
|
111
|
+
const batches = this.chunkArray(runResults, batchSize);
|
|
82
112
|
|
|
83
|
-
logger.info(`Submitting ${
|
|
113
|
+
logger.info(`Submitting ${runResults.length} results for run ${runId} in ${batches.length} batches`);
|
|
84
114
|
|
|
85
115
|
for (let i = 0; i < batches.length; i++) {
|
|
86
116
|
const batch = batches[i];
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
const payload = {
|
|
118
|
+
run_id: runId,
|
|
119
|
+
results: batch.map(r => this.buildAutomationResultPayload(runId, r, runMetadata))
|
|
120
|
+
};
|
|
89
121
|
|
|
90
|
-
logger.debug('Batch payload', { payload: JSON.stringify(
|
|
122
|
+
logger.debug('Batch payload', { payload: JSON.stringify(payload, null, 2) });
|
|
123
|
+
|
|
124
|
+
// Show UUIDs being submitted (test execution status shown, NOT backend validation status)
|
|
125
|
+
console.log('\nš¤ Submitting batch with UUIDs:');
|
|
126
|
+
payload.results.forEach((r, idx) => {
|
|
127
|
+
console.log(` ${idx + 1}. ${r.test_case_uuid}`);
|
|
128
|
+
});
|
|
91
129
|
|
|
92
130
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
131
|
+
const response = await this.http.post('/api/automation/result/batch', payload);
|
|
132
|
+
|
|
133
|
+
// DEBUG: Show batch response with context
|
|
134
|
+
console.log('\nš ========== BATCH SUBMIT RESPONSE ==========');
|
|
135
|
+
console.log('Run ID:', runId);
|
|
136
|
+
console.log('Batch:', `${i + 1}/${batches.length}`);
|
|
137
|
+
console.log('Results in batch:', batch.length);
|
|
138
|
+
if (response.data && response.data.debug_context) {
|
|
139
|
+
console.log('Project ID (from API):', response.data.debug_context.project_id);
|
|
140
|
+
console.log('User ID (from API):', response.data.debug_context.uid);
|
|
141
|
+
} else {
|
|
142
|
+
console.log('ā ļø No debug_context in response');
|
|
143
|
+
}
|
|
144
|
+
console.log('Status:', response.status);
|
|
145
|
+
console.log('Success:', response.success);
|
|
146
|
+
|
|
147
|
+
// Check for validation failures - response has nested data object
|
|
148
|
+
const failedResults = response.data?.data?.results?.failed || response.data?.results?.failed || response.data?.failed || [];
|
|
149
|
+
const submittedResults = response.data?.data?.results?.submitted || response.data?.results?.submitted || response.data?.submitted || [];
|
|
150
|
+
const submittedCount = typeof submittedResults === 'number' ? submittedResults : (Array.isArray(submittedResults) ? submittedResults.length : 0);
|
|
151
|
+
|
|
152
|
+
if (failedResults.length > 0) {
|
|
153
|
+
console.log('\nā ========== VALIDATION FAILURES ==========');
|
|
154
|
+
console.log(` ā
ACCEPTED: ${submittedCount} result(s)`);
|
|
155
|
+
console.log(` ā REJECTED: ${failedResults.length} result(s)\n`);
|
|
156
|
+
failedResults.forEach((fail, idx) => {
|
|
157
|
+
const uuid = fail.test_case_uuid || fail.uuid;
|
|
158
|
+
const error = fail.error || fail.message;
|
|
159
|
+
const code = fail.code;
|
|
160
|
+
|
|
161
|
+
console.log(` ${idx + 1}. UUID: ${uuid}`);
|
|
162
|
+
console.log(` ā Reason: ${error}`);
|
|
163
|
+
if (code) {
|
|
164
|
+
console.log(` š« HTTP Code: ${code}`);
|
|
165
|
+
}
|
|
166
|
+
console.log('');
|
|
167
|
+
});
|
|
168
|
+
console.log('===========================================');
|
|
169
|
+
}
|
|
170
|
+
console.log('============================================\n');
|
|
95
171
|
|
|
96
|
-
// Check if request was successful
|
|
97
172
|
if (!response.success) {
|
|
98
173
|
throw new Error(response.error || 'Batch submission failed');
|
|
99
174
|
}
|
|
100
175
|
|
|
101
|
-
//
|
|
102
|
-
|
|
176
|
+
// Only count results that were actually accepted by backend
|
|
177
|
+
// Backend may reject some results due to validation (e.g., project mismatch)
|
|
178
|
+
if (failedResults.length > 0) {
|
|
179
|
+
// Some results were rejected - need to filter based on UUIDs
|
|
180
|
+
const rejectedUuids = new Set(failedResults.map(f => f.test_case_uuid || f.uuid));
|
|
181
|
+
|
|
182
|
+
batch.forEach(result => {
|
|
183
|
+
if (rejectedUuids.has(result.uuid)) {
|
|
184
|
+
// This result was rejected by backend validation
|
|
185
|
+
failures.push(result);
|
|
186
|
+
} else {
|
|
187
|
+
// This result was accepted
|
|
188
|
+
allResults.push(result);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
// All results accepted
|
|
193
|
+
allResults.push(...batch);
|
|
194
|
+
}
|
|
103
195
|
|
|
104
|
-
// Call progress callback
|
|
105
196
|
if (onProgress) {
|
|
106
197
|
onProgress({
|
|
107
|
-
|
|
198
|
+
runId,
|
|
108
199
|
batchIndex: i + 1,
|
|
109
200
|
totalBatches: batches.length,
|
|
110
|
-
completed: (i + 1) * batchSize,
|
|
111
|
-
total:
|
|
201
|
+
completed: Math.min((i + 1) * batchSize, runResults.length),
|
|
202
|
+
total: runResults.length
|
|
112
203
|
});
|
|
113
204
|
}
|
|
114
205
|
|
|
115
|
-
logger.debug(`Batch ${i + 1}/${batches.length} submitted for ${
|
|
206
|
+
logger.debug(`Batch ${i + 1}/${batches.length} submitted for run ${runId}`, {
|
|
116
207
|
count: batch.length
|
|
117
208
|
});
|
|
118
209
|
} catch (error) {
|
|
119
|
-
logger.error(`Batch ${i + 1}/${batches.length} failed for ${
|
|
210
|
+
logger.error(`Batch ${i + 1}/${batches.length} failed for run ${runId}`, {
|
|
120
211
|
error: error.message,
|
|
212
|
+
status: error.status,
|
|
121
213
|
count: batch.length
|
|
122
214
|
});
|
|
123
215
|
failures.push(...batch);
|
|
@@ -211,6 +303,33 @@ class ResultService {
|
|
|
211
303
|
};
|
|
212
304
|
}
|
|
213
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Build result payload for automation endpoints
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
buildAutomationResultPayload(runId, result, runMetadata = {}) {
|
|
311
|
+
const statusMap = {
|
|
312
|
+
passed: 'passed',
|
|
313
|
+
failed: 'failed',
|
|
314
|
+
skipped: 'skipped',
|
|
315
|
+
timedOut: 'failed',
|
|
316
|
+
interrupted: 'skipped'
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
run_id: runId || result.runId,
|
|
321
|
+
test_case_uuid: result.uuid,
|
|
322
|
+
status: statusMap[result.status] || result.status,
|
|
323
|
+
duration: result.duration || 0,
|
|
324
|
+
error_message: result.error || result.comment || '',
|
|
325
|
+
browser: normalizeBrowser(result.browser || 'Unknown Browser'),
|
|
326
|
+
device: result.device || runMetadata.device || 'Desktop',
|
|
327
|
+
os: result.os || runMetadata.os || '',
|
|
328
|
+
environment: runMetadata.environment || 'Local',
|
|
329
|
+
timestamp: result.timestamp || Math.floor(Date.now() / 1000)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
214
333
|
/**
|
|
215
334
|
* Group results by browser
|
|
216
335
|
* @private
|
|
@@ -238,6 +357,21 @@ class ResultService {
|
|
|
238
357
|
return chunks;
|
|
239
358
|
}
|
|
240
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Group results by runId
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
groupByRun(results) {
|
|
365
|
+
return results.reduce((acc, result) => {
|
|
366
|
+
const runId = result.runId || result.run_id || 'unknown';
|
|
367
|
+
if (!acc[runId]) {
|
|
368
|
+
acc[runId] = [];
|
|
369
|
+
}
|
|
370
|
+
acc[runId].push(result);
|
|
371
|
+
return acc;
|
|
372
|
+
}, {});
|
|
373
|
+
}
|
|
374
|
+
|
|
241
375
|
/**
|
|
242
376
|
* Extract NID from UUID
|
|
243
377
|
* @private
|
|
@@ -22,6 +22,22 @@ class RunMatrixService {
|
|
|
22
22
|
*/
|
|
23
23
|
async create(options) {
|
|
24
24
|
try {
|
|
25
|
+
if (this.config?.options?.enableAppq === false) {
|
|
26
|
+
logger.info('Appq disabled - returning local demo run (no Appq entry created)');
|
|
27
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28
|
+
return {
|
|
29
|
+
runId: `demo-run-${now}`,
|
|
30
|
+
token: null,
|
|
31
|
+
timestamp: now,
|
|
32
|
+
metadata: {
|
|
33
|
+
appqEnabled: false,
|
|
34
|
+
mode: 'demo',
|
|
35
|
+
environment: options.environment || 'Local',
|
|
36
|
+
browsers: options.browsers || ['Chrome']
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
// Validate required fields
|
|
26
42
|
this.validateCreateOptions(options);
|
|
27
43
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const logger = require('../utils/logger');
|
|
2
|
+
|
|
3
|
+
class TaggingService {
|
|
4
|
+
constructor(httpClient, config = { options: {} }) {
|
|
5
|
+
this.http = httpClient;
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fire-and-forget tagging for accepted UUIDs.
|
|
11
|
+
* Does not throw; logs warnings on failure.
|
|
12
|
+
* @param {string[]} uuids
|
|
13
|
+
* @param {string} tagName
|
|
14
|
+
*/
|
|
15
|
+
async tagAccepted(uuids = [], tagName) {
|
|
16
|
+
if (!uuids || uuids.length === 0) return;
|
|
17
|
+
|
|
18
|
+
if (this.config?.options?.enableAppq === false) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const unique = Array.from(new Set(uuids.filter(Boolean)));
|
|
23
|
+
if (unique.length === 0) return;
|
|
24
|
+
|
|
25
|
+
const tag = tagName || this.config?.options?.autoTagName || process.env.APPLIQATION_TAG_NAME || 'Appq_automated';
|
|
26
|
+
const endpoint = this.config?.options?.autoTagEndpoint || process.env.APPLIQATION_TAG_ENDPOINT || '/api/automation/testcase/tag';
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await this.http.post(endpoint, {
|
|
30
|
+
uuids: unique,
|
|
31
|
+
tag
|
|
32
|
+
});
|
|
33
|
+
logger.debug('Tagging submitted for accepted results', { count: unique.length, tag });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.warn('Failed to tag accepted results (non-blocking)', {
|
|
36
|
+
error: error.message,
|
|
37
|
+
count: unique.length,
|
|
38
|
+
tag
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = TaggingService;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/utils/errors.js
CHANGED
|
File without changes
|
package/src/utils/index.js
CHANGED
|
File without changes
|
package/src/utils/logger.js
CHANGED
|
File without changes
|
package/src/utils/mapAppqUuid.js
CHANGED
|
File without changes
|
package/src/utils/validator.js
CHANGED
|
File without changes
|