@appliqation/automation-sdk 2.5.1 → 2.8.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.
@@ -1,408 +0,0 @@
1
- const AppliqationClient = require('../../AppliqationClient');
2
- const UuidExtractor = require('./UuidExtractor');
3
- const logger = require('../../utils/logger');
4
-
5
- function toBoolean(value) {
6
- if (value === undefined || value === null) return undefined;
7
- if (typeof value === 'boolean') return value;
8
- const normalized = String(value).trim().toLowerCase();
9
- if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
10
- if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
11
- return undefined;
12
- }
13
-
14
- function parseCliEnableFlag() {
15
- const argv = process.argv || [];
16
- for (let i = 0; i < argv.length; i++) {
17
- const arg = argv[i];
18
- if (arg.startsWith('--appq=')) {
19
- return toBoolean(arg.split('=')[1]);
20
- }
21
- if (arg === '--appq') {
22
- return true;
23
- }
24
- if (arg.startsWith('--enable-appq=')) {
25
- return toBoolean(arg.split('=')[1]);
26
- }
27
- if (arg === '--enable-appq') {
28
- return true;
29
- }
30
- }
31
- return undefined;
32
- }
33
-
34
- function resolveEnableAppq(config) {
35
- const cliValue = parseCliEnableFlag();
36
- const envValue = process.env.APPLIQATION_ENABLE ?? process.env.APPQ ?? process.env.APPLIQATION_APPQ;
37
- const configValue = config?.enableAppq ?? config?.options?.enableAppq;
38
- const resolved = toBoolean(configValue) ?? toBoolean(cliValue) ?? toBoolean(envValue);
39
- return resolved !== undefined ? resolved : true;
40
- }
41
-
42
- /**
43
- * Appliqation Reporter for Jest
44
- *
45
- * Custom Jest reporter that automatically reports test results
46
- * to the Appliqation platform.
47
- *
48
- * @example
49
- * // jest.config.js
50
- * const { JestReporter } = require('@appliqation/automation-sdk/jest');
51
- *
52
- * module.exports = {
53
- * reporters: [
54
- * 'default',
55
- * [JestReporter, {
56
- * baseUrl: process.env.APPLIQATION_BASE_URL,
57
- * apiKey: process.env.APPLIQATION_API_KEY,
58
- * projectKey: process.env.APPLIQATION_PROJECT_KEY,
59
- * scenarioId: parseInt(process.env.APPLIQATION_SCENARIO_ID),
60
- * environment: process.env.APPLIQATION_ENVIRONMENT || 'Local',
61
- * title: process.env.APPLIQATION_RUN_TITLE
62
- * }]
63
- * ]
64
- * };
65
- */
66
- class JestReporter {
67
- constructor(globalConfig, options) {
68
- this.globalConfig = globalConfig;
69
- this.config = {
70
- baseUrl: options.baseUrl,
71
- apiKey: options.apiKey,
72
- projectKey: options.projectKey,
73
- scenarioId: options.scenarioId,
74
- testSetId: options.testSetId,
75
- environment: options.environment || 'Local',
76
- title: options.title || process.env.APPLIQATION_RUN_TITLE,
77
- autoCreateRun: options.autoCreateRun !== false,
78
- logLevel: options.logLevel || 'info',
79
- submitOrphans: options.submitOrphans !== false,
80
- enableAppq: resolveEnableAppq(options)
81
- };
82
-
83
- // Validate required config
84
- this.validateConfig();
85
-
86
- // Set logger level
87
- logger.setLevel(this.config.logLevel);
88
-
89
- // Initialize Appliqation client
90
- this.client = new AppliqationClient({
91
- baseUrl: this.config.baseUrl,
92
- apiKey: this.config.apiKey,
93
- projectKey: this.config.projectKey,
94
- title: this.config.title,
95
- options: {
96
- logLevel: this.config.logLevel,
97
- enableAppq: this.config.enableAppq
98
- }
99
- });
100
-
101
- if (this.config.enableAppq === false) {
102
- logger.warn('Appq disabled via flag. Runs/results will not be sent to Appliqation.');
103
- }
104
-
105
- // Test result storage
106
- this.testResults = [];
107
- this.orphanTests = [];
108
- this.runId = null;
109
- this.runCreated = false;
110
-
111
- logger.info('Appliqation Jest Reporter initialized', {
112
- environment: this.config.environment,
113
- scenarioId: this.config.scenarioId,
114
- autoCreateRun: this.config.autoCreateRun
115
- });
116
- }
117
-
118
- /**
119
- * Called once at the start of the test run
120
- */
121
- async onRunStart(results, options) {
122
- try {
123
- logger.info('Jest test run starting...', {
124
- numTotalTestSuites: results.numTotalTestSuites
125
- });
126
-
127
- if (this.config.autoCreateRun) {
128
- await this.createRun();
129
- }
130
- } catch (error) {
131
- logger.error('Error in onRunStart', { error: error.message });
132
- }
133
- }
134
-
135
- /**
136
- * Create Appliqation run matrix
137
- */
138
- async createRun() {
139
- try {
140
- const os = this.detectOS();
141
-
142
- const runOptions = {
143
- scenarioId: this.config.scenarioId,
144
- testSetId: this.config.testSetId,
145
- environment: this.config.environment,
146
- browsers: ['Node.js'],
147
- device: 'Server',
148
- os: os,
149
- title: this.config.title || `Automation Run - ${new Date().toISOString()}`
150
- };
151
-
152
- logger.info('Creating run matrix...', runOptions);
153
-
154
- const run = await this.client.createRun(runOptions);
155
- this.runId = run.runId;
156
- this.runCreated = true;
157
-
158
- logger.info('Run matrix created successfully', {
159
- runId: this.runId
160
- });
161
- } catch (error) {
162
- logger.error('Failed to create run matrix', { error: error.message });
163
- throw error;
164
- }
165
- }
166
-
167
- /**
168
- * Called after each test file completes
169
- */
170
- async onTestFileResult(test, testResult, results) {
171
- try {
172
- logger.debug('Processing test file results', {
173
- testFilePath: testResult.testFilePath,
174
- numTests: testResult.testResults.length
175
- });
176
-
177
- // Process all tests in this file
178
- for (const result of testResult.testResults) {
179
- await this.processTestResult(result, testResult);
180
- }
181
- } catch (error) {
182
- logger.error('Error processing test file results', {
183
- error: error.message,
184
- file: testResult.testFilePath
185
- });
186
- }
187
- }
188
-
189
- /**
190
- * Process individual test result
191
- */
192
- async processTestResult(testResult, fileResult) {
193
- try {
194
- // Extract UUID from test
195
- const uuid = UuidExtractor.extractUuid(testResult);
196
-
197
- if (!uuid) {
198
- // Track orphan test
199
- this.orphanTests.push({
200
- title: testResult.fullName || testResult.title,
201
- status: this.mapJestStatus(testResult.status),
202
- file: fileResult.testFilePath,
203
- timestamp: new Date().toISOString()
204
- });
205
-
206
- logger.warn('No UUID found for test', {
207
- title: testResult.fullName || testResult.title
208
- });
209
- return;
210
- }
211
-
212
- // Prepare result
213
- const result = {
214
- uuid: uuid,
215
- runId: this.runId,
216
- status: this.mapJestStatus(testResult.status),
217
- browser: 'Jest',
218
- environment: this.config.environment,
219
- comment: this.buildComment(testResult),
220
- parent_uuid: null // Jest doesn't have nested tests in the same way
221
- };
222
-
223
- this.testResults.push(result);
224
-
225
- logger.debug('Test result prepared', {
226
- uuid: uuid,
227
- status: result.status,
228
- title: testResult.fullName
229
- });
230
- } catch (error) {
231
- logger.error('Error processing test result', {
232
- title: testResult.fullName,
233
- error: error.message
234
- });
235
- }
236
- }
237
-
238
- /**
239
- * Called after all tests complete
240
- */
241
- async onRunComplete(contexts, results) {
242
- try {
243
- logger.info('Jest test run completed', {
244
- tests: this.testResults.length,
245
- orphans: this.orphanTests.length,
246
- numTotalTests: results.numTotalTests,
247
- numPassedTests: results.numPassedTests,
248
- numFailedTests: results.numFailedTests
249
- });
250
-
251
- // Submit all test results
252
- if (this.testResults.length > 0 && this.runId) {
253
- await this.submitResults();
254
- }
255
-
256
- // Submit orphan tests
257
- if (this.orphanTests.length > 0 && this.config.submitOrphans && this.runId) {
258
- await this.submitOrphanTests();
259
- }
260
-
261
- // Print summary
262
- this.printSummary(results);
263
- } catch (error) {
264
- logger.error('Error in onRunComplete', { error: error.message });
265
- }
266
- }
267
-
268
- /**
269
- * Submit test results to Appliqation
270
- */
271
- async submitResults() {
272
- try {
273
- logger.info(`Submitting ${this.testResults.length} test results...`);
274
-
275
- const summary = await this.client.submitBatch(this.testResults, {
276
- batchSize: 50,
277
- retryFailures: true
278
- });
279
-
280
- logger.info('Results submitted successfully', {
281
- success: summary.success,
282
- failed: summary.failed,
283
- total: summary.total
284
- });
285
-
286
- return summary;
287
- } catch (error) {
288
- logger.error('Failed to submit results', { error: error.message });
289
- throw error;
290
- }
291
- }
292
-
293
- /**
294
- * Submit orphan tests
295
- */
296
- async submitOrphanTests() {
297
- try {
298
- logger.info(`Logging ${this.orphanTests.length} orphan tests...`);
299
-
300
- await this.client.logOrphanTests(this.runId, this.orphanTests);
301
-
302
- logger.info('Orphan tests logged successfully');
303
- } catch (error) {
304
- logger.error('Failed to log orphan tests', { error: error.message });
305
- }
306
- }
307
-
308
- /**
309
- * Map Jest test status to Appliqation status
310
- */
311
- mapJestStatus(jestStatus) {
312
- const statusMap = {
313
- 'passed': 'Pass', // Capital case to match backend expectations
314
- 'failed': 'Fail', // Capital case to match backend expectations
315
- 'skipped': 'Skipped', // Capital case to match backend expectations
316
- 'pending': 'Skipped',
317
- 'todo': 'Skipped',
318
- 'disabled': 'Skipped'
319
- };
320
-
321
- return statusMap[jestStatus] || 'Skipped';
322
- }
323
-
324
- /**
325
- * Build comment from test details
326
- */
327
- buildComment(testResult) {
328
- const comments = [];
329
-
330
- if (testResult.failureMessages && testResult.failureMessages.length > 0) {
331
- // Get first failure message (Jest can have multiple)
332
- const errorMsg = testResult.failureMessages[0];
333
- // Truncate to first 200 chars to avoid too long comments
334
- const truncated = errorMsg.substring(0, 200);
335
- comments.push(`Error: ${truncated}${errorMsg.length > 200 ? '...' : ''}`);
336
- }
337
-
338
- if (testResult.duration) {
339
- comments.push(`Duration: ${testResult.duration}ms`);
340
- }
341
-
342
- if (testResult.numPassingAsserts) {
343
- comments.push(`Assertions: ${testResult.numPassingAsserts}`);
344
- }
345
-
346
- return comments.length > 0 ? comments.join(' | ') : null;
347
- }
348
-
349
- /**
350
- * Detect OS
351
- */
352
- detectOS() {
353
- const platform = process.platform;
354
-
355
- if (platform === 'darwin') return 'macOS';
356
- if (platform === 'win32') return 'Windows';
357
- if (platform === 'linux') return 'Linux';
358
-
359
- return platform;
360
- }
361
-
362
- /**
363
- * Print test summary
364
- */
365
- printSummary(results) {
366
- console.log('\n' + '='.repeat(60));
367
- console.log('📊 Appliqation Jest Reporter Summary');
368
- console.log('='.repeat(60));
369
- console.log(`Environment: ${this.config.environment}`);
370
- console.log(`Run ID: ${this.runId || 'N/A'}`);
371
- console.log(`Tests Submitted: ${this.testResults.length}`);
372
- console.log(`Orphan Tests: ${this.orphanTests.length}`);
373
- console.log(`Total Tests: ${results.numTotalTests}`);
374
- console.log(`Passed: ${results.numPassedTests}`);
375
- console.log(`Failed: ${results.numFailedTests}`);
376
- console.log(`Skipped: ${results.numPendingTests}`);
377
- console.log(`Duration: ${(results.startTime && ((Date.now() - results.startTime) / 1000).toFixed(2))}s`);
378
- console.log('='.repeat(60) + '\n');
379
- }
380
-
381
- /**
382
- * Validate configuration
383
- */
384
- validateConfig() {
385
- if (this.config.enableAppq === false) {
386
- return;
387
- }
388
-
389
- const required = ['baseUrl', 'apiKey', 'projectKey'];
390
- const missing = required.filter(key => !this.config[key]);
391
-
392
- if (missing.length > 0) {
393
- throw new Error(`Missing required config: ${missing.join(', ')}`);
394
- }
395
-
396
- // Note: scenarioId and testSetId are now optional
397
- // If neither is provided, will default to 0 for generic automation runs
398
- }
399
-
400
- /**
401
- * Get last error (Jest API requirement)
402
- */
403
- getLastError() {
404
- return this.lastError;
405
- }
406
- }
407
-
408
- module.exports = JestReporter;
@@ -1,174 +0,0 @@
1
- const UuidValidator = require('../../utils/UuidValidator');
2
- const logger = require('../../utils/logger');
3
-
4
- /**
5
- * UUID Extractor for Jest Tests
6
- *
7
- * Extracts Appliqation test case UUIDs from Jest tests.
8
- * Supports multiple UUID assignment methods.
9
- */
10
- class UuidExtractor {
11
- /**
12
- * Extract UUID from Jest test result
13
- *
14
- * Supports multiple UUID formats:
15
- * 1. Test title with UUID prefix: test('1154-... - should login', () => {})
16
- * 2. Docblock with @uuid tag: /** @uuid 1154-... *\/
17
- * 3. Custom test metadata (if available)
18
- *
19
- * @param {Object} testResult - Jest test result object
20
- * @returns {string|null} - Extracted UUID or null
21
- */
22
- static extractUuid(testResult) {
23
- try {
24
- // Method 1: Extract from test title/fullName
25
- const fullName = testResult.fullName || testResult.title;
26
- if (fullName) {
27
- const uuidFromTitle = this.extractUuidFromString(fullName);
28
- if (uuidFromTitle) {
29
- logger.debug('UUID extracted from test title', { uuid: uuidFromTitle });
30
- return uuidFromTitle;
31
- }
32
- }
33
-
34
- // Method 2: Extract from ancestorTitles + title
35
- if (testResult.ancestorTitles && testResult.title) {
36
- const combinedTitle = [...testResult.ancestorTitles, testResult.title].join(' ');
37
- const uuidFromCombined = this.extractUuidFromString(combinedTitle);
38
- if (uuidFromCombined) {
39
- logger.debug('UUID extracted from combined titles', { uuid: uuidFromCombined });
40
- return uuidFromCombined;
41
- }
42
- }
43
-
44
- // Method 3: Check each ancestor title individually
45
- if (testResult.ancestorTitles) {
46
- for (const ancestorTitle of testResult.ancestorTitles) {
47
- const uuidFromAncestor = this.extractUuidFromString(ancestorTitle);
48
- if (uuidFromAncestor) {
49
- logger.debug('UUID extracted from ancestor title', { uuid: uuidFromAncestor });
50
- return uuidFromAncestor;
51
- }
52
- }
53
- }
54
-
55
- // Method 4: Check test title alone
56
- if (testResult.title) {
57
- const uuidFromTestTitle = this.extractUuidFromString(testResult.title);
58
- if (uuidFromTestTitle) {
59
- logger.debug('UUID extracted from test title alone', { uuid: uuidFromTestTitle });
60
- return uuidFromTestTitle;
61
- }
62
- }
63
-
64
- // No UUID found
65
- return null;
66
- } catch (error) {
67
- logger.error('Error extracting UUID from Jest test', {
68
- error: error.message,
69
- test: testResult.title
70
- });
71
- return null;
72
- }
73
- }
74
-
75
- /**
76
- * Extract UUID from string using regex pattern
77
- *
78
- * @param {string} str - String to search
79
- * @returns {string|null} - Extracted UUID or null
80
- */
81
- static extractUuidFromString(str) {
82
- if (!str || typeof str !== 'string') {
83
- return null;
84
- }
85
-
86
- // Pattern: nid-uuid format (e.g., "1154-7a17b809-0ff9-4ba1-9322-4eb2a49abfc5")
87
- const uuidPattern = /(\d+)-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i;
88
- const match = str.match(uuidPattern);
89
-
90
- if (match && match[0]) {
91
- const uuid = match[0];
92
- if (UuidValidator.validate(uuid)) {
93
- return uuid;
94
- }
95
- }
96
-
97
- return null;
98
- }
99
-
100
- /**
101
- * Extract UUID from Jest docblock comment
102
- *
103
- * Example: /** @uuid 1154-7a17b809-0ff9-4ba1-9322-4eb2a49abfc5 *\/
104
- *
105
- * @param {string} docblock - Docblock comment string
106
- * @returns {string|null} - Extracted UUID or null
107
- */
108
- static extractUuidFromDocblock(docblock) {
109
- if (!docblock || typeof docblock !== 'string') {
110
- return null;
111
- }
112
-
113
- // Pattern: @uuid tag in docblock
114
- const docblockPattern = /@uuid\s+(\d+-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i;
115
- const match = docblock.match(docblockPattern);
116
-
117
- if (match && match[1]) {
118
- const uuid = match[1];
119
- if (UuidValidator.validate(uuid)) {
120
- return uuid;
121
- }
122
- }
123
-
124
- return null;
125
- }
126
-
127
- /**
128
- * Extract all UUIDs from a collection of tests
129
- *
130
- * @param {Array} testResults - Array of Jest test result objects
131
- * @returns {Object} - { uuids: [...], orphans: [...] }
132
- */
133
- static extractFromTests(testResults) {
134
- const results = {
135
- uuids: [],
136
- orphans: []
137
- };
138
-
139
- if (!Array.isArray(testResults)) {
140
- return results;
141
- }
142
-
143
- for (const testResult of testResults) {
144
- const uuid = this.extractUuid(testResult);
145
-
146
- if (uuid) {
147
- results.uuids.push({
148
- uuid: uuid,
149
- test: testResult
150
- });
151
- } else {
152
- results.orphans.push({
153
- title: testResult.fullName || testResult.title,
154
- test: testResult
155
- });
156
- }
157
- }
158
-
159
- return results;
160
- }
161
-
162
- /**
163
- * Validate if test has a valid UUID
164
- *
165
- * @param {Object} testResult - Jest test result object
166
- * @returns {boolean} - True if test has valid UUID
167
- */
168
- static hasValidUuid(testResult) {
169
- const uuid = this.extractUuid(testResult);
170
- return uuid !== null;
171
- }
172
- }
173
-
174
- module.exports = UuidExtractor;
@@ -1,28 +0,0 @@
1
- /**
2
- * Appliqation Jest Reporter
3
- *
4
- * @example
5
- * // jest.config.js
6
- * const { JestReporter } = require('@appliqation/automation-sdk/jest');
7
- *
8
- * module.exports = {
9
- * reporters: [
10
- * 'default',
11
- * [JestReporter, {
12
- * baseUrl: process.env.APPLIQATION_BASE_URL,
13
- * apiKey: process.env.APPLIQATION_API_KEY,
14
- * projectKey: process.env.APPLIQATION_PROJECT_KEY,
15
- * scenarioId: parseInt(process.env.APPLIQATION_SCENARIO_ID),
16
- * environment: process.env.APPLIQATION_ENVIRONMENT || 'Local',
17
- * title: process.env.APPLIQATION_RUN_TITLE
18
- * }]
19
- * ]
20
- * };
21
- */
22
-
23
- const JestReporter = require('./JestReporter');
24
- const UuidExtractor = require('./UuidExtractor');
25
-
26
- module.exports = JestReporter;
27
- module.exports.JestReporter = JestReporter;
28
- module.exports.UuidExtractor = UuidExtractor;