@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.
Files changed (38) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +260 -20
  3. package/package.json +9 -1
  4. package/src/AppliqationClient.js +1 -1
  5. package/src/constants.js +0 -0
  6. package/src/core/AuthManager.js +0 -0
  7. package/src/core/HttpClient.js +0 -0
  8. package/src/index.d.ts +0 -0
  9. package/src/index.js +0 -0
  10. package/src/playwright/JwtBrowserAuth.js +0 -0
  11. package/src/playwright/fixture.js +0 -0
  12. package/src/playwright/global-setup.js +43 -0
  13. package/src/playwright/global-teardown.js +42 -0
  14. package/src/playwright/helpers/jwt-browser-auth.js +0 -0
  15. package/src/playwright/index.js +0 -0
  16. package/src/reporters/cypress/CypressReporter.js +49 -2
  17. package/src/reporters/cypress/UuidExtractor.js +0 -0
  18. package/src/reporters/cypress/index.js +0 -0
  19. package/src/reporters/jest/JestReporter.js +49 -2
  20. package/src/reporters/jest/UuidExtractor.js +0 -0
  21. package/src/reporters/jest/index.js +0 -0
  22. package/src/reporters/playwright/AppliqationReporter.js +424 -22
  23. package/src/reporters/playwright/helpers/DeviceOsDetector.js +0 -0
  24. package/src/reporters/playwright/helpers/UuidExtractor.js +0 -0
  25. package/src/reporters/playwright/index.d.ts +0 -0
  26. package/src/reporters/playwright/index.js +0 -0
  27. package/src/services/OrphanTestService.js +0 -0
  28. package/src/services/ResultService.js +158 -24
  29. package/src/services/RunMatrixService.js +16 -0
  30. package/src/services/TaggingService.js +44 -0
  31. package/src/utils/PayloadBuilder.js +0 -0
  32. package/src/utils/RunDataNormalizer.js +0 -0
  33. package/src/utils/UuidValidator.js +0 -0
  34. package/src/utils/errors.js +0 -0
  35. package/src/utils/index.js +0 -0
  36. package/src/utils/logger.js +0 -0
  37. package/src/utils/mapAppqUuid.js +0 -0
  38. 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
- const payload = [this.buildLegacyResultPayload(result, runMetadata)];
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 existing /api/insert/scenario/result endpoint (expects array)
27
- const response = await this.http.post('/api/insert/scenario/result', payload);
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 browser for efficient metadata updates
75
- const grouped = this.groupByBrowser(results);
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 [browser, browserResults] of Object.entries(grouped)) {
81
- const batches = this.chunkArray(browserResults, batchSize);
110
+ for (const [runId, runResults] of Object.entries(groupedByRun)) {
111
+ const batches = this.chunkArray(runResults, batchSize);
82
112
 
83
- logger.info(`Submitting ${browserResults.length} results for ${browser} in ${batches.length} batches`);
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
- // Transform results to legacy format for existing endpoint
88
- const payloads = batch.map(r => this.buildLegacyResultPayload(r, runMetadata));
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(payloads, null, 2) });
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
- // Use existing /api/insert/scenario/result endpoint
94
- const response = await this.http.post('/api/insert/scenario/result', payloads);
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
- // Track successful submissions
102
- allResults.push(...batch);
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
- browser,
198
+ runId,
108
199
  batchIndex: i + 1,
109
200
  totalBatches: batches.length,
110
- completed: (i + 1) * batchSize,
111
- total: browserResults.length
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 ${browser}`, {
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 ${browser}`, {
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
File without changes
File without changes
File without changes
File without changes
File without changes