@appliqation/automation-sdk 2.1.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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +441 -0
  3. package/package.json +107 -0
  4. package/src/AppliqationClient.js +562 -0
  5. package/src/constants.js +245 -0
  6. package/src/core/AuthManager.js +353 -0
  7. package/src/core/HttpClient.js +475 -0
  8. package/src/index.d.ts +333 -0
  9. package/src/index.js +26 -0
  10. package/src/playwright/JwtBrowserAuth.js +240 -0
  11. package/src/playwright/fixture.js +92 -0
  12. package/src/playwright/global-setup.js +243 -0
  13. package/src/playwright/helpers/jwt-browser-auth.js +227 -0
  14. package/src/playwright/index.js +16 -0
  15. package/src/reporters/cypress/CypressReporter.js +387 -0
  16. package/src/reporters/cypress/UuidExtractor.js +139 -0
  17. package/src/reporters/cypress/index.js +30 -0
  18. package/src/reporters/jest/JestReporter.js +361 -0
  19. package/src/reporters/jest/UuidExtractor.js +174 -0
  20. package/src/reporters/jest/index.js +28 -0
  21. package/src/reporters/playwright/AppliqationReporter.js +654 -0
  22. package/src/reporters/playwright/helpers/DeviceOsDetector.js +435 -0
  23. package/src/reporters/playwright/helpers/UuidExtractor.js +290 -0
  24. package/src/reporters/playwright/index.d.ts +96 -0
  25. package/src/reporters/playwright/index.js +14 -0
  26. package/src/services/OrphanTestService.js +74 -0
  27. package/src/services/ResultService.js +252 -0
  28. package/src/services/RunMatrixService.js +309 -0
  29. package/src/utils/PayloadBuilder.js +280 -0
  30. package/src/utils/RunDataNormalizer.js +335 -0
  31. package/src/utils/UuidValidator.js +102 -0
  32. package/src/utils/errors.js +217 -0
  33. package/src/utils/index.js +17 -0
  34. package/src/utils/logger.js +124 -0
  35. package/src/utils/mapAppqUuid.js +83 -0
  36. package/src/utils/validator.js +157 -0
@@ -0,0 +1,290 @@
1
+ const UuidValidator = require('../../../utils/UuidValidator');
2
+
3
+ /**
4
+ * UUID Extractor for Playwright Tests
5
+ * Extracts Appliqation test UUIDs from various locations in Playwright test metadata
6
+ */
7
+ class UuidExtractor {
8
+ /**
9
+ * Extract UUID from Playwright test
10
+ * Searches in order: test.id, test tags, test title, test annotations
11
+ * @param {Object} test - Playwright test object
12
+ * @returns {string|null} UUID or null if not found
13
+ */
14
+ static extractFromTest(test) {
15
+ if (!test) return null;
16
+
17
+ // 1. Check test.id directly (if user sets it)
18
+ if (test.id && UuidValidator.validate(test.id)) {
19
+ return test.id;
20
+ }
21
+
22
+ // 2. Check test tags (@uuid-123-xxx-xxx-xxx)
23
+ const tagUuid = this.extractFromTags(test.tags || []);
24
+ if (tagUuid) return tagUuid;
25
+
26
+ // 3. Check test title (extract UUID from title string)
27
+ const titleUuid = this.extractFromTitle(test.title);
28
+ if (titleUuid) return titleUuid;
29
+
30
+ // 4. Check test annotations
31
+ const annotationUuid = this.extractFromAnnotations(test.annotations || []);
32
+ if (annotationUuid) return annotationUuid;
33
+
34
+ // 5. Check test location (file path or test name)
35
+ const locationUuid = this.extractFromLocation(test.location);
36
+ if (locationUuid) return locationUuid;
37
+
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * Extract UUID from test tags
43
+ * Supports formats:
44
+ * - @uuid:123-xxx-xxx-xxx
45
+ * - @appliqation:123-xxx-xxx-xxx
46
+ * - @123-xxx-xxx-xxx
47
+ * @param {Array<string>} tags - Array of test tags
48
+ * @returns {string|null} UUID or null
49
+ */
50
+ static extractFromTags(tags) {
51
+ if (!Array.isArray(tags) || tags.length === 0) return null;
52
+
53
+ for (const tag of tags) {
54
+ if (!tag || typeof tag !== 'string') continue;
55
+
56
+ // Remove @ prefix if present
57
+ const cleanTag = tag.startsWith('@') ? tag.substring(1) : tag;
58
+
59
+ // Check for uuid: or appliqation: prefix
60
+ const prefixMatch = cleanTag.match(/^(?:uuid|appliqation):(.+)$/i);
61
+ if (prefixMatch) {
62
+ const uuid = prefixMatch[1];
63
+ if (UuidValidator.validate(uuid)) {
64
+ return uuid;
65
+ }
66
+ }
67
+
68
+ // Check if tag itself is a UUID
69
+ if (UuidValidator.validate(cleanTag)) {
70
+ return cleanTag;
71
+ }
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Extract UUID from test title
79
+ * Supports formats:
80
+ * - "Test name [123-xxx-xxx-xxx]"
81
+ * - "Test name (123-xxx-xxx-xxx)"
82
+ * - "Test name | 123-xxx-xxx-xxx"
83
+ * - "123-xxx-xxx-xxx | Test name"
84
+ * @param {string} title - Test title
85
+ * @returns {string|null} UUID or null
86
+ */
87
+ static extractFromTitle(title) {
88
+ if (!title || typeof title !== 'string') return null;
89
+
90
+ // Pattern 1: [UUID] or (UUID) at the end
91
+ const bracketMatch = title.match(/[\[\(](\d+-[a-f0-9-]+)[\]\)]$/i);
92
+ if (bracketMatch && UuidValidator.validate(bracketMatch[1])) {
93
+ return bracketMatch[1];
94
+ }
95
+
96
+ // Pattern 2: | UUID or UUID | at the beginning or end
97
+ const pipeMatch = title.match(/(?:^|\|)\s*(\d+-[a-f0-9-]+)\s*(?:\||$)/i);
98
+ if (pipeMatch && UuidValidator.validate(pipeMatch[1])) {
99
+ return pipeMatch[1];
100
+ }
101
+
102
+ // Pattern 3: UUID anywhere in the string
103
+ const anywhereMatch = title.match(/\b(\d+-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\b/i);
104
+ if (anywhereMatch && UuidValidator.validate(anywhereMatch[1])) {
105
+ return anywhereMatch[1];
106
+ }
107
+
108
+ return null;
109
+ }
110
+
111
+ /**
112
+ * Extract UUID from test annotations
113
+ * Looks for annotations with type 'uuid' or 'appliqation'
114
+ * @param {Array<Object>} annotations - Array of annotation objects
115
+ * @returns {string|null} UUID or null
116
+ */
117
+ static extractFromAnnotations(annotations) {
118
+ if (!Array.isArray(annotations) || annotations.length === 0) return null;
119
+
120
+ for (const annotation of annotations) {
121
+ if (!annotation || typeof annotation !== 'object') continue;
122
+
123
+ // Check annotation type
124
+ if (annotation.type === 'uuid' || annotation.type === 'appliqation' || annotation.type === 'appliqation-uuid') {
125
+ const uuid = annotation.description || annotation.value;
126
+ if (uuid && UuidValidator.validate(uuid)) {
127
+ return uuid;
128
+ }
129
+ }
130
+
131
+ // Check annotation description for UUID
132
+ if (annotation.description) {
133
+ const uuid = this.extractFromTitle(annotation.description);
134
+ if (uuid) return uuid;
135
+ }
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ /**
142
+ * Extract UUID from test location (file path or test name)
143
+ * @param {Object} location - Test location object
144
+ * @returns {string|null} UUID or null
145
+ */
146
+ static extractFromLocation(location) {
147
+ if (!location || typeof location !== 'object') return null;
148
+
149
+ // Check file path for UUID
150
+ if (location.file) {
151
+ const fileMatch = location.file.match(/(\d+-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
152
+ if (fileMatch && UuidValidator.validate(fileMatch[1])) {
153
+ return fileMatch[1];
154
+ }
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ /**
161
+ * Extract all UUIDs from test suite (for parent-child relationships)
162
+ * @param {Object} test - Playwright test object
163
+ * @returns {Array<string>} Array of UUIDs (child first, parents after)
164
+ */
165
+ static extractHierarchy(test) {
166
+ const uuids = [];
167
+
168
+ if (!test) return uuids;
169
+
170
+ // Get current test UUID
171
+ const testUuid = this.extractFromTest(test);
172
+ if (testUuid) {
173
+ uuids.push(testUuid);
174
+ }
175
+
176
+ // Get parent suite UUIDs
177
+ let currentSuite = test.parent;
178
+ while (currentSuite) {
179
+ const suiteUuid = this.extractFromTest(currentSuite);
180
+ if (suiteUuid) {
181
+ uuids.push(suiteUuid);
182
+ }
183
+ currentSuite = currentSuite.parent;
184
+ }
185
+
186
+ return uuids;
187
+ }
188
+
189
+ /**
190
+ * Create orphan test entry from Playwright test
191
+ * @param {Object} test - Playwright test object
192
+ * @param {Object} result - Test result object
193
+ * @param {string} browser - Browser name
194
+ * @returns {Object} Orphan test entry
195
+ */
196
+ static createOrphanEntry(test, result, browser) {
197
+ return {
198
+ test_title: test.title || 'Unknown Test',
199
+ test_file: test.location?.file || 'unknown',
200
+ browser: browser || 'unknown',
201
+ timestamp: Math.floor(Date.now() / 1000),
202
+ reason: 'No UUID annotation found',
203
+ framework: 'playwright',
204
+ additional_info: {
205
+ projectName: test.parent?.project?.name,
206
+ duration: result?.duration,
207
+ status: result?.status,
208
+ tags: test.tags
209
+ }
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Validate and extract UUID with detailed error info
215
+ * @param {Object} test - Playwright test object
216
+ * @returns {Object} { uuid, found, locations }
217
+ */
218
+ static extractWithDetails(test) {
219
+ const locations = {
220
+ id: false,
221
+ tags: false,
222
+ title: false,
223
+ annotations: false,
224
+ location: false
225
+ };
226
+
227
+ let uuid = null;
228
+
229
+ // Check all locations
230
+ if (test.id && UuidValidator.validate(test.id)) {
231
+ uuid = test.id;
232
+ locations.id = true;
233
+ }
234
+
235
+ if (!uuid) {
236
+ const tagUuid = this.extractFromTags(test.tags || []);
237
+ if (tagUuid) {
238
+ uuid = tagUuid;
239
+ locations.tags = true;
240
+ }
241
+ }
242
+
243
+ if (!uuid) {
244
+ const titleUuid = this.extractFromTitle(test.title);
245
+ if (titleUuid) {
246
+ uuid = titleUuid;
247
+ locations.title = true;
248
+ }
249
+ }
250
+
251
+ if (!uuid) {
252
+ const annotationUuid = this.extractFromAnnotations(test.annotations || []);
253
+ if (annotationUuid) {
254
+ uuid = annotationUuid;
255
+ locations.annotations = true;
256
+ }
257
+ }
258
+
259
+ if (!uuid) {
260
+ const locationUuid = this.extractFromLocation(test.location);
261
+ if (locationUuid) {
262
+ uuid = locationUuid;
263
+ locations.location = true;
264
+ }
265
+ }
266
+
267
+ return {
268
+ uuid,
269
+ found: uuid !== null,
270
+ locations
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Get extraction suggestions for test without UUID
276
+ * @param {Object} test - Playwright test object
277
+ * @returns {Array<string>} Suggestions for adding UUID
278
+ */
279
+ static getSuggestions(test) {
280
+ const suggestions = [];
281
+
282
+ suggestions.push(`Add UUID as tag: test('${test.title}', { tag: '@uuid:123-xxx-...' }, async ({ page }) => { ... })`);
283
+ suggestions.push(`Add UUID in title: test('[123-xxx-...] ${test.title}', async ({ page }) => { ... })`);
284
+ suggestions.push(`Add UUID as annotation: test.use({ annotation: { type: 'uuid', description: '123-xxx-...' } })`);
285
+
286
+ return suggestions;
287
+ }
288
+ }
289
+
290
+ module.exports = UuidExtractor;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * TypeScript definitions for @appliqation/automation-sdk/playwright
3
+ * @version 2.1.0
4
+ */
5
+
6
+ import type {
7
+ Reporter,
8
+ FullConfig,
9
+ Suite,
10
+ TestCase,
11
+ TestResult,
12
+ FullResult
13
+ } from '@playwright/test/reporter';
14
+
15
+ import type { AppliqationConfig } from '../../index';
16
+
17
+ // ============================================================================
18
+ // Playwright Reporter Configuration
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Configuration for the Appliqation Playwright Reporter
23
+ */
24
+ export interface AppliqationReporterConfig extends AppliqationConfig {
25
+ /** Log level for reporter (default: 'INFO') */
26
+ logLevel?: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
27
+
28
+ /** Log orphan tests without UUID mappings (default: true) */
29
+ logOrphans?: boolean;
30
+ }
31
+
32
+ // ============================================================================
33
+ // Appliqation Playwright Reporter
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Playwright reporter for Appliqation integration
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * // playwright.config.ts
42
+ * import { AppliqationReporter } from '@appliqation/automation-sdk/playwright';
43
+ *
44
+ * export default {
45
+ * reporter: [
46
+ * ['list'],
47
+ * [AppliqationReporter, {
48
+ * baseUrl: process.env.APPLIQATION_BASE_URL,
49
+ * apiKey: process.env.APPLIQATION_API_KEY,
50
+ * projectKey: process.env.APPLIQATION_PROJECT_KEY,
51
+ * scenarioId: 0,
52
+ * environment: 'Local',
53
+ * logLevel: 'INFO'
54
+ * }]
55
+ * ]
56
+ * };
57
+ * ```
58
+ */
59
+ export class AppliqationReporter implements Reporter {
60
+ /**
61
+ * Create a new Appliqation Playwright Reporter
62
+ * @param config - Reporter configuration
63
+ */
64
+ constructor(config: AppliqationReporterConfig);
65
+
66
+ /**
67
+ * Called once before running tests
68
+ */
69
+ onBegin(config: FullConfig, suite: Suite): Promise<void>;
70
+
71
+ /**
72
+ * Called after a test has been started
73
+ */
74
+ onTestBegin?(test: TestCase, result: TestResult): void;
75
+
76
+ /**
77
+ * Called after a test has been finished
78
+ */
79
+ onTestEnd?(test: TestCase, result: TestResult): void;
80
+
81
+ /**
82
+ * Called after all tests have been run
83
+ */
84
+ onEnd(result: FullResult): Promise<void>;
85
+
86
+ /**
87
+ * Process error
88
+ */
89
+ onError?(error: Error): void;
90
+ }
91
+
92
+ // ============================================================================
93
+ // Exports
94
+ // ============================================================================
95
+
96
+ export default AppliqationReporter;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Appliqation Playwright Reporter
3
+ * Main export for Playwright integration
4
+ */
5
+
6
+ const AppliqationReporter = require('./AppliqationReporter');
7
+ const UuidExtractor = require('./helpers/UuidExtractor');
8
+ const DeviceOsDetector = require('./helpers/DeviceOsDetector');
9
+
10
+ module.exports = AppliqationReporter;
11
+ module.exports.AppliqationReporter = AppliqationReporter;
12
+ module.exports.UuidExtractor = UuidExtractor;
13
+ module.exports.DeviceOsDetector = DeviceOsDetector;
14
+ module.exports.default = AppliqationReporter;
@@ -0,0 +1,74 @@
1
+ const logger = require('../utils/logger');
2
+
3
+ class OrphanTestService {
4
+ constructor(httpClient) {
5
+ this.http = httpClient;
6
+ }
7
+
8
+ /**
9
+ * Log orphan tests (tests without UUID mappings)
10
+ * @param {string} runId - Run ID
11
+ * @param {Array} orphanTests - Array of orphan test objects
12
+ * @returns {Promise<Object>} Response from server
13
+ */
14
+ async log(runId, orphanTests) {
15
+ if (!orphanTests || orphanTests.length === 0) {
16
+ logger.debug('No orphan tests to log');
17
+ return {
18
+ success: true,
19
+ logged: 0
20
+ };
21
+ }
22
+
23
+ try {
24
+ logger.info(`Logging ${orphanTests.length} orphan tests for run ${runId}`);
25
+
26
+ const payload = {
27
+ run_id: runId,
28
+ orphan_tests: orphanTests.map(test => ({
29
+ test_title: test.test_title || test.title || 'Unknown Test',
30
+ test_file: test.test_file || test.file || 'unknown',
31
+ browser: test.browser || 'unknown',
32
+ timestamp: test.timestamp || Math.floor(Date.now() / 1000),
33
+ reason: test.reason || 'No UUID annotation found',
34
+ framework: test.framework || 'unknown'
35
+ }))
36
+ };
37
+
38
+ const response = await this.http.post('/api/automation/orphan-tests', payload);
39
+
40
+ logger.info('Orphan tests logged successfully', {
41
+ runId,
42
+ count: response.logged || orphanTests.length
43
+ });
44
+
45
+ return response;
46
+ } catch (error) {
47
+ logger.error('Failed to log orphan tests', {
48
+ error: error.message,
49
+ runId,
50
+ count: orphanTests.length
51
+ });
52
+ throw new Error(`Failed to log orphan tests: ${error.message}`);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Create formatted orphan test entry
58
+ * @param {Object} test - Test object
59
+ * @param {string} reason - Reason for being orphaned
60
+ * @returns {Object} Formatted orphan test
61
+ */
62
+ createOrphanEntry(test, reason = 'No UUID annotation found') {
63
+ return {
64
+ test_title: test.title || test.name || 'Unknown Test',
65
+ test_file: test.file || test.location?.file || 'unknown',
66
+ browser: test.browser || 'unknown',
67
+ timestamp: Math.floor(Date.now() / 1000),
68
+ reason: reason,
69
+ framework: test.framework || 'unknown'
70
+ };
71
+ }
72
+ }
73
+
74
+ module.exports = OrphanTestService;