@appliqation/automation-sdk 2.1.7 → 2.1.9
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 +18 -79
- package/package.json +1 -1
- package/src/AppliqationClient.js +27 -17
- package/src/constants.js +11 -1
- package/src/playwright/fixture.js +2 -1
- package/src/playwright/global-setup.js +31 -45
- package/src/reporters/playwright/AppliqationReporter.js +160 -34
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ Before you begin, ensure you have:
|
|
|
32
32
|
|
|
33
33
|
3. **Appliqation Account** with:
|
|
34
34
|
- Access to Appliqation portal
|
|
35
|
-
- API key (get from your project settings)
|
|
36
|
-
- Project key (get from your project settings)
|
|
35
|
+
- API key (get from your project settings or ask your project admin)
|
|
36
|
+
- Project key (get from your project settings or ask your project admin)
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
@@ -61,18 +61,15 @@ Create a `.env` file in your project root with your Appliqation credentials:
|
|
|
61
61
|
```bash
|
|
62
62
|
# IMPORTANT: No spaces around = signs, no trailing slashes on URLs
|
|
63
63
|
|
|
64
|
-
APPLIQATION_BASE_URL=https://your-domain.appliqation.com
|
|
65
64
|
APPLIQATION_API_KEY=appq_live_xxxxxxxxxxxxxxxxxxxx
|
|
66
65
|
APPLIQATION_PROJECT_KEY=your-project-key-here
|
|
67
|
-
|
|
68
|
-
APPLIQATION_RUN_TITLE=Give-name-to-your-testrun
|
|
66
|
+
TEST_APP_URL=https://www.example.com # App under test (Playwright baseURL)
|
|
69
67
|
```
|
|
70
68
|
|
|
71
69
|
**How to get these values:**
|
|
72
|
-
- `APPLIQATION_BASE_URL` - Your Appliqation portal URL (no trailing slash!)
|
|
73
70
|
- `APPLIQATION_API_KEY` - Found in your project settings
|
|
74
71
|
- `APPLIQATION_PROJECT_KEY` - Found in your project settings
|
|
75
|
-
- `
|
|
72
|
+
- `TEST_APP_URL` - The site you’re testing (e.g., https://www.amazon.in). Used as Playwright `baseURL`.
|
|
76
73
|
-
|
|
77
74
|
|
|
78
75
|
**⚠️ Add .env to .gitignore** to keep credentials secret:
|
|
@@ -94,11 +91,8 @@ require('dotenv').config(); // Load .env variables
|
|
|
94
91
|
|
|
95
92
|
// SDK configuration
|
|
96
93
|
const appliqationConfig = {
|
|
97
|
-
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
98
94
|
apiKey: process.env.APPLIQATION_API_KEY,
|
|
99
95
|
projectKey: process.env.APPLIQATION_PROJECT_KEY,
|
|
100
|
-
environment: process.env.APPLIQATION_ENVIRONMENT,
|
|
101
|
-
title: process.env.APPLIQATION_RUN_TITLE, // Custom run title (optional)
|
|
102
96
|
|
|
103
97
|
// Optional settings
|
|
104
98
|
autoCreateRun: true, // Automatically create run on test start
|
|
@@ -117,7 +111,7 @@ module.exports = defineConfig({
|
|
|
117
111
|
globalTeardown: require.resolve('@appliqation/automation-sdk/playwright/global-teardown'),
|
|
118
112
|
|
|
119
113
|
use: {
|
|
120
|
-
baseURL: process.env.
|
|
114
|
+
baseURL: process.env.TEST_APP_URL,
|
|
121
115
|
|
|
122
116
|
// Use SDK-managed authentication state
|
|
123
117
|
storageState: '.auth/jwt.json',
|
|
@@ -205,7 +199,8 @@ test('should display user profile', async ({ page }, testInfo) => {
|
|
|
205
199
|
Run your Playwright tests as normal:
|
|
206
200
|
|
|
207
201
|
```bash
|
|
208
|
-
npx playwright test
|
|
202
|
+
APPLIQATION_RUN_TITLE=yourruntitle APPLIQATION_ENVIRONMENT=test_env_name npx playwright test
|
|
203
|
+
|
|
209
204
|
```
|
|
210
205
|
|
|
211
206
|
**What happens:**
|
|
@@ -257,8 +252,8 @@ Understanding the SDK's authentication and result submission flow helps troubles
|
|
|
257
252
|
│ 1. Read .env configuration │
|
|
258
253
|
│ ├─ APPLIQATION_API_KEY │
|
|
259
254
|
│ ├─ APPLIQATION_PROJECT_KEY │
|
|
260
|
-
│ ├─
|
|
261
|
-
|
|
255
|
+
│ ├─ TEST_APP_URL │
|
|
256
|
+
│
|
|
262
257
|
│ │ │
|
|
263
258
|
│ ▼ │
|
|
264
259
|
│ 2. Create automation run via API │
|
|
@@ -408,9 +403,8 @@ your-playwright-project/
|
|
|
408
403
|
├── .env # YOU CREATE THIS (Step 2)
|
|
409
404
|
│ ├── APPLIQATION_API_KEY=appq_live_xxxxx
|
|
410
405
|
│ ├── APPLIQATION_PROJECT_KEY=your-project-key
|
|
411
|
-
│ ├──
|
|
412
|
-
│
|
|
413
|
-
│ └── APPLIQATION_ENVIRONMENT=Local
|
|
406
|
+
│ ├── TEST_APP_URL=https://your-domain.com
|
|
407
|
+
│
|
|
414
408
|
│
|
|
415
409
|
├── playwright.config.js # YOU UPDATE THIS (Step 3)
|
|
416
410
|
│ ├── globalSetup: require.resolve('@appliqation/automation-sdk/playwright/global-setup')
|
|
@@ -449,37 +443,10 @@ All environment variables and their purposes:
|
|
|
449
443
|
|
|
450
444
|
| Variable | Required | Description | Example |
|
|
451
445
|
|----------|----------|-------------|---------|
|
|
452
|
-
| `
|
|
446
|
+
| `TEST_APP_URL` | ✅ Yes | Your Test App URL (no trailing slash) | `https://example.com` |
|
|
453
447
|
| `APPLIQATION_API_KEY` | ✅ Yes | API key from `/admin/config/appliqation/api-keys` | `appq_live_abc123xyz...` |
|
|
454
448
|
| `APPLIQATION_PROJECT_KEY` | ✅ Yes | Project key from project settings | `my-project-key` |
|
|
455
|
-
| `APPLIQATION_SCENARIO_ID` | ✅ Yes | Scenario node ID to track | `1154` |
|
|
456
|
-
| `APPLIQATION_ENVIRONMENT` | ❌ No | Environment name (default: `Local`) | `Staging`, `Production` |
|
|
457
|
-
| `APPLIQATION_RUN_TITLE` | ❌ No | Custom run title (default: auto-generated) | `Sprint 24 - Regression` |
|
|
458
|
-
| `LOG_LEVEL` | ❌ No | Logging verbosity (default: `INFO`) | `DEBUG`, `ERROR` |
|
|
459
|
-
|
|
460
|
-
### Reporter Options (playwright.config.js)
|
|
461
|
-
|
|
462
|
-
All available options for the Appliqation reporter config:
|
|
463
449
|
|
|
464
|
-
```javascript
|
|
465
|
-
const appliqationConfig = {
|
|
466
|
-
// Required
|
|
467
|
-
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
468
|
-
apiKey: process.env.APPLIQATION_API_KEY,
|
|
469
|
-
projectKey: process.env.APPLIQATION_PROJECT_KEY,
|
|
470
|
-
scenarioId: parseInt(process.env.APPLIQATION_SCENARIO_ID),
|
|
471
|
-
|
|
472
|
-
// Optional
|
|
473
|
-
environment: 'Local', // Environment name
|
|
474
|
-
title: 'My Custom Run', // Custom run title
|
|
475
|
-
autoCreateRun: true, // Auto-create run (default: true)
|
|
476
|
-
batchSubmit: true, // Batch results (default: true)
|
|
477
|
-
batchSize: 50, // Results per batch (default: 50)
|
|
478
|
-
logOrphans: true, // Log tests without UUIDs (default: true)
|
|
479
|
-
logLevel: 'INFO', // INFO, DEBUG, ERROR (default: INFO)
|
|
480
|
-
rejectUnauthorized: false // Allow self-signed certs (default: false)
|
|
481
|
-
};
|
|
482
|
-
```
|
|
483
450
|
|
|
484
451
|
**Common Configurations:**
|
|
485
452
|
|
|
@@ -598,13 +565,13 @@ Tests without `mapAppqUuid()` calls are called **orphan tests**. The SDK will:
|
|
|
598
565
|
}]
|
|
599
566
|
```
|
|
600
567
|
|
|
601
|
-
4. Check `.env` has correct `
|
|
568
|
+
4. Check `.env` has correct `TEST_APP_URL` (no trailing slash):
|
|
602
569
|
```bash
|
|
603
570
|
# ❌ Wrong
|
|
604
|
-
|
|
571
|
+
TEST_APP_URL=https://portal.com/
|
|
605
572
|
|
|
606
573
|
# ✅ Correct
|
|
607
|
-
|
|
574
|
+
TEST_APP_URL=https://portal.com
|
|
608
575
|
```
|
|
609
576
|
|
|
610
577
|
---
|
|
@@ -666,11 +633,8 @@ Add `rejectUnauthorized: false` to your SDK configuration in `playwright.config.
|
|
|
666
633
|
|
|
667
634
|
```javascript
|
|
668
635
|
const appliqationConfig = {
|
|
669
|
-
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
670
636
|
apiKey: process.env.APPLIQATION_API_KEY,
|
|
671
637
|
projectKey: process.env.APPLIQATION_PROJECT_KEY,
|
|
672
|
-
environment: process.env.APPLIQATION_ENVIRONMENT,
|
|
673
|
-
title: process.env.APPLIQATION_RUN_TITLE,
|
|
674
638
|
|
|
675
639
|
// Add this for self-signed certificates
|
|
676
640
|
rejectUnauthorized: false // ⚠️ Only use in development!
|
|
@@ -689,14 +653,11 @@ const appliqationConfig = {
|
|
|
689
653
|
- **Fix:** Add UUID mapping to all tests
|
|
690
654
|
- Enable `logOrphans: true` to see which tests are orphans
|
|
691
655
|
|
|
692
|
-
2. **
|
|
693
|
-
- **Fix:** Verify `APPLIQATION_SCENARIO_ID` matches portal
|
|
694
|
-
|
|
695
|
-
3. **API key invalid** - Authentication failed
|
|
656
|
+
2. **API key invalid** - Authentication failed
|
|
696
657
|
- **Fix:** Regenerate API key from portal
|
|
697
658
|
|
|
698
|
-
|
|
699
|
-
- **Fix:** Check `
|
|
659
|
+
3. **Network issues** - Portal unreachable
|
|
660
|
+
- **Fix:** Check `TEST_APP_URL` is accessible
|
|
700
661
|
|
|
701
662
|
---
|
|
702
663
|
|
|
@@ -737,7 +698,6 @@ require('dotenv').config();
|
|
|
737
698
|
|
|
738
699
|
async function testConnection() {
|
|
739
700
|
const client = new AppliqationClient({
|
|
740
|
-
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
741
701
|
apiKey: process.env.APPLIQATION_API_KEY,
|
|
742
702
|
projectKey: process.env.APPLIQATION_PROJECT_KEY
|
|
743
703
|
});
|
|
@@ -796,14 +756,12 @@ const AppliqationClient = require('@appliqation/automation-sdk');
|
|
|
796
756
|
|
|
797
757
|
// Initialize client
|
|
798
758
|
const client = new AppliqationClient({
|
|
799
|
-
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
800
759
|
apiKey: process.env.APPLIQATION_API_KEY,
|
|
801
760
|
projectKey: process.env.APPLIQATION_PROJECT_KEY
|
|
802
761
|
});
|
|
803
762
|
|
|
804
763
|
// Create run
|
|
805
764
|
const run = await client.createRun({
|
|
806
|
-
scenarioId: 1154,
|
|
807
765
|
environment: 'Production',
|
|
808
766
|
browsers: ['Chrome'],
|
|
809
767
|
device: 'Desktop',
|
|
@@ -838,25 +796,6 @@ Set custom titles for test runs:
|
|
|
838
796
|
```bash
|
|
839
797
|
APPLIQATION_RUN_TITLE="Sprint 24 - Regression Tests" npx playwright test
|
|
840
798
|
```
|
|
841
|
-
|
|
842
|
-
**Method 2: Config File**
|
|
843
|
-
```javascript
|
|
844
|
-
const appliqationConfig = {
|
|
845
|
-
// ... other config
|
|
846
|
-
title: "Sprint 24 - Regression Tests"
|
|
847
|
-
};
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
**Method 3: Programmatic (Core SDK)**
|
|
851
|
-
```javascript
|
|
852
|
-
const client = new AppliqationClient({
|
|
853
|
-
// ... other config
|
|
854
|
-
title: "Sprint 24 - Regression Tests"
|
|
855
|
-
});
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
**Priority:** Config > Env Var > Default (`"Automation Run - {timestamp}"`)
|
|
859
|
-
|
|
860
799
|
---
|
|
861
800
|
|
|
862
801
|
## API Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appliqation/automation-sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.9",
|
|
4
4
|
"description": "Appliqation Automation SDK with API key authentication, custom run titles, and framework-specific reporters",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
package/src/AppliqationClient.js
CHANGED
|
@@ -6,6 +6,7 @@ const OrphanTestService = require('./services/OrphanTestService');
|
|
|
6
6
|
const UuidValidator = require('./utils/UuidValidator');
|
|
7
7
|
const PayloadBuilder = require('./utils/PayloadBuilder');
|
|
8
8
|
const logger = require('./utils/logger');
|
|
9
|
+
const { DEFAULT_APPLIQATION_BASE_URL } = require('./constants');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Main Appliqation Automation SDK Client
|
|
@@ -51,16 +52,23 @@ class AppliqationClient {
|
|
|
51
52
|
* @param {string} [config.options.logLevel='info'] - Logging level
|
|
52
53
|
*/
|
|
53
54
|
constructor(config) {
|
|
55
|
+
// Apply SDK default for baseUrl when not provided
|
|
56
|
+
const resolvedBaseUrl = (config?.baseUrl || process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL || '').replace(/\/$/, '');
|
|
57
|
+
const normalizedConfig = {
|
|
58
|
+
...(config || {}),
|
|
59
|
+
baseUrl: resolvedBaseUrl
|
|
60
|
+
};
|
|
61
|
+
|
|
54
62
|
// Validate configuration
|
|
55
|
-
this.validateConfig(
|
|
63
|
+
this.validateConfig(normalizedConfig);
|
|
56
64
|
|
|
57
65
|
// Determine SSL enforcement based on environment
|
|
58
66
|
const isProduction = this._isProductionEnvironment();
|
|
59
67
|
let rejectUnauthorized = true; // Default to secure
|
|
60
68
|
|
|
61
|
-
if (
|
|
69
|
+
if (normalizedConfig.rejectUnauthorized !== undefined) {
|
|
62
70
|
// User explicitly set rejectUnauthorized
|
|
63
|
-
if (isProduction &&
|
|
71
|
+
if (isProduction && normalizedConfig.rejectUnauthorized === false) {
|
|
64
72
|
// WARNING: User is trying to disable SSL in production
|
|
65
73
|
logger.warn('SSL certificate verification is disabled in production environment!', {
|
|
66
74
|
environment: process.env.NODE_ENV,
|
|
@@ -68,7 +76,7 @@ class AppliqationClient {
|
|
|
68
76
|
warning: 'This is a security risk. SSL verification should be enabled in production.'
|
|
69
77
|
});
|
|
70
78
|
}
|
|
71
|
-
rejectUnauthorized =
|
|
79
|
+
rejectUnauthorized = normalizedConfig.rejectUnauthorized;
|
|
72
80
|
} else if (isProduction) {
|
|
73
81
|
// Production environment - force SSL verification
|
|
74
82
|
rejectUnauthorized = true;
|
|
@@ -80,18 +88,18 @@ class AppliqationClient {
|
|
|
80
88
|
|
|
81
89
|
// Store configuration
|
|
82
90
|
this.config = {
|
|
83
|
-
baseUrl:
|
|
84
|
-
apiKey:
|
|
85
|
-
projectKey:
|
|
86
|
-
username:
|
|
87
|
-
password:
|
|
88
|
-
runTitle:
|
|
91
|
+
baseUrl: resolvedBaseUrl,
|
|
92
|
+
apiKey: normalizedConfig.apiKey,
|
|
93
|
+
projectKey: normalizedConfig.projectKey,
|
|
94
|
+
username: normalizedConfig.username,
|
|
95
|
+
password: normalizedConfig.password,
|
|
96
|
+
runTitle: normalizedConfig.runTitle || normalizedConfig.title || process.env.APPLIQATION_RUN_TITLE || null,
|
|
89
97
|
rejectUnauthorized: rejectUnauthorized,
|
|
90
98
|
options: {
|
|
91
|
-
timeout:
|
|
92
|
-
retries:
|
|
93
|
-
logOrphans:
|
|
94
|
-
logLevel:
|
|
99
|
+
timeout: normalizedConfig.options?.timeout || 30000,
|
|
100
|
+
retries: normalizedConfig.options?.retries || 3,
|
|
101
|
+
logOrphans: normalizedConfig.options?.logOrphans !== false,
|
|
102
|
+
logLevel: normalizedConfig.options?.logLevel || 'info'
|
|
95
103
|
}
|
|
96
104
|
};
|
|
97
105
|
|
|
@@ -110,10 +118,12 @@ class AppliqationClient {
|
|
|
110
118
|
// Track current run context
|
|
111
119
|
this.currentRun = null;
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
// Show baseUrl only in DEBUG mode
|
|
122
|
+
const meta = {
|
|
115
123
|
authMode: this.config.apiKey ? 'api_key' : 'csrf'
|
|
116
|
-
}
|
|
124
|
+
};
|
|
125
|
+
logger.info('Appliqation client initialized', meta);
|
|
126
|
+
logger.debug('Client configuration', { baseUrl: this.config.baseUrl });
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
/**
|
package/src/constants.js
CHANGED
|
@@ -197,6 +197,13 @@ const DEFAULT_DEVICE = 'Desktop';
|
|
|
197
197
|
*/
|
|
198
198
|
const DEFAULT_OS = 'Unknown';
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Default Appliqation portal URL
|
|
202
|
+
* Used when APPLIQATION_BASE_URL is not provided by the user
|
|
203
|
+
* @constant {string}
|
|
204
|
+
*/
|
|
205
|
+
const DEFAULT_APPLIQATION_BASE_URL = 'https://appq.appliqation.io';
|
|
206
|
+
|
|
200
207
|
// ============================================================================
|
|
201
208
|
// Exports
|
|
202
209
|
// ============================================================================
|
|
@@ -241,5 +248,8 @@ module.exports = {
|
|
|
241
248
|
DEFAULT_SCENARIO_ID,
|
|
242
249
|
DEFAULT_ENVIRONMENT,
|
|
243
250
|
DEFAULT_DEVICE,
|
|
244
|
-
DEFAULT_OS
|
|
251
|
+
DEFAULT_OS,
|
|
252
|
+
|
|
253
|
+
// Base URL
|
|
254
|
+
DEFAULT_APPLIQATION_BASE_URL
|
|
245
255
|
};
|
|
@@ -10,6 +10,7 @@ try {
|
|
|
10
10
|
|
|
11
11
|
const JwtBrowserAuth = require('./JwtBrowserAuth');
|
|
12
12
|
require('dotenv').config();
|
|
13
|
+
const { DEFAULT_APPLIQATION_BASE_URL } = require('../constants');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Appliqation Playwright Fixture with Auto JWT Refresh
|
|
@@ -42,7 +43,7 @@ if (baseTest) {
|
|
|
42
43
|
test = baseTest.extend({
|
|
43
44
|
page: async ({ page }, use) => {
|
|
44
45
|
// Get configuration from environment
|
|
45
|
-
const baseUrl = process.env.APPLIQATION_BASE_URL;
|
|
46
|
+
const baseUrl = process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL;
|
|
46
47
|
const apiKey = process.env.APPLIQATION_API_KEY;
|
|
47
48
|
const projectKey = process.env.APPLIQATION_PROJECT_KEY;
|
|
48
49
|
|
|
@@ -27,6 +27,7 @@ const axios = require('axios');
|
|
|
27
27
|
const https = require('https');
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
|
+
const { DEFAULT_APPLIQATION_BASE_URL } = require('../constants');
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Setup JWT-based browser authentication
|
|
@@ -35,11 +36,27 @@ const path = require('path');
|
|
|
35
36
|
* @returns {Promise<void>}
|
|
36
37
|
*/
|
|
37
38
|
async function globalSetup(config) {
|
|
38
|
-
console.log('\n
|
|
39
|
+
console.log('\n🚀 Global Setup: Initializing Test Environment...\n');
|
|
39
40
|
|
|
40
41
|
// Extract SDK configuration from reporter config or use env
|
|
41
42
|
const sdkConfig = extractSDKConfig(config);
|
|
42
43
|
|
|
44
|
+
// Display startup information
|
|
45
|
+
console.log(`🌍 Environment: ${sdkConfig.environment || 'Not specified'}`);
|
|
46
|
+
console.log(`🔗 Test App URL: ${sdkConfig.appUrl || 'Not specified'}`);
|
|
47
|
+
|
|
48
|
+
// Show Run ID and Timestamp only in DEBUG mode
|
|
49
|
+
if (process.env.LOG_LEVEL === 'DEBUG') {
|
|
50
|
+
const crypto = require('crypto');
|
|
51
|
+
const runId = crypto.randomUUID();
|
|
52
|
+
console.log(`📊 Run ID: ${runId}`);
|
|
53
|
+
console.log(`⏰ Timestamp: ${new Date().toISOString()}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`🔑 API Key: ${sdkConfig.apiKey ? 'Configured' : 'Not set'}\n`);
|
|
57
|
+
|
|
58
|
+
console.log('🔐 Setting up JWT browser authentication...\n');
|
|
59
|
+
|
|
43
60
|
// Validate required configuration
|
|
44
61
|
if (!sdkConfig.apiKey) {
|
|
45
62
|
console.error('❌ APPLIQATION_API_KEY is required for JWT authentication');
|
|
@@ -71,8 +88,7 @@ async function globalSetup(config) {
|
|
|
71
88
|
return;
|
|
72
89
|
}
|
|
73
90
|
|
|
74
|
-
console.log(`✅ JWT token received (expires in 1 hour)`);
|
|
75
|
-
console.log(` Run ID: ${jwtData.run_id}\n`);
|
|
91
|
+
console.log(`✅ JWT token received (expires in 1 hour)\n`);
|
|
76
92
|
|
|
77
93
|
// Step 2: Setup browser with JWT
|
|
78
94
|
console.log('🌐 Setting up browser session with JWT...');
|
|
@@ -82,9 +98,8 @@ async function globalSetup(config) {
|
|
|
82
98
|
console.log('📄 Authenticated state saved to: .auth/jwt.json\n');
|
|
83
99
|
console.log('✨ Tests will run with JWT authentication!\n');
|
|
84
100
|
|
|
85
|
-
// Store for use in tests
|
|
101
|
+
// Store for use in tests (run_id will be set by reporter when tests execute)
|
|
86
102
|
process.env.APPLIQATION_JWT_TOKEN = jwtData.jwt_token;
|
|
87
|
-
process.env.APPLIQATION_RUN_ID = jwtData.run_id;
|
|
88
103
|
|
|
89
104
|
} catch (error) {
|
|
90
105
|
console.error('\n❌ JWT authentication setup failed:', error.message);
|
|
@@ -124,13 +139,15 @@ function extractSDKConfig(config) {
|
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
// Fallback to environment variables
|
|
142
|
+
const baseUrl = sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL;
|
|
143
|
+
|
|
127
144
|
return {
|
|
128
|
-
baseUrl
|
|
145
|
+
baseUrl,
|
|
129
146
|
apiKey: sdkConfig.apiKey || process.env.APPLIQATION_API_KEY,
|
|
130
147
|
projectKey: sdkConfig.projectKey || process.env.APPLIQATION_PROJECT_KEY,
|
|
131
148
|
scenarioId: sdkConfig.scenarioId || parseInt(process.env.APPLIQATION_SCENARIO_ID) || 0,
|
|
132
149
|
environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT || 'Local',
|
|
133
|
-
appUrl: sdkConfig.appUrl || process.env.APPLIQATION_APP_URL ||
|
|
150
|
+
appUrl: sdkConfig.appUrl || process.env.APPLIQATION_APP_URL || process.env.TEST_APP_URL || baseUrl,
|
|
134
151
|
useJwtAuth: sdkConfig.useJwtAuth !== undefined ? sdkConfig.useJwtAuth : process.env.APPLIQATION_USE_JWT_AUTH !== 'false',
|
|
135
152
|
runTitle: sdkConfig.runTitle || process.env.APPLIQATION_RUN_TITLE || `JWT Auth Setup - ${new Date().toISOString()}`,
|
|
136
153
|
};
|
|
@@ -143,46 +160,15 @@ function extractSDKConfig(config) {
|
|
|
143
160
|
* @returns {Promise<Object>} JWT data { jwt_token, run_id, user_id, project_id }
|
|
144
161
|
*/
|
|
145
162
|
async function getJWTToken(sdkConfig) {
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
environment: sdkConfig.environment,
|
|
153
|
-
title: sdkConfig.runTitle,
|
|
154
|
-
browsers: ['Chrome'], // Default browser for setup
|
|
155
|
-
device: 'Desktop',
|
|
156
|
-
// IMPORTANT: Always use Windows for JWT setup run to avoid WSL2/Linux detection issues
|
|
157
|
-
// The actual test runs will detect the correct OS from browser user agents
|
|
158
|
-
os: process.platform === 'darwin' ? 'macOS' : 'Windows', // Default to Windows (not Linux)
|
|
163
|
+
// Get browser session JWT token directly (no need to create run matrix)
|
|
164
|
+
// The SDK reporter will create run matrices automatically when tests execute
|
|
165
|
+
const jwtEndpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/auth/jwt/browser`;
|
|
166
|
+
|
|
167
|
+
const jwtPayload = {
|
|
168
|
+
api_key: sdkConfig.apiKey
|
|
159
169
|
};
|
|
160
170
|
|
|
161
171
|
try {
|
|
162
|
-
const runResponse = await axios.post(createRunEndpoint, runPayload, {
|
|
163
|
-
headers: {
|
|
164
|
-
'Content-Type': 'application/json',
|
|
165
|
-
'X-API-Key': sdkConfig.apiKey,
|
|
166
|
-
},
|
|
167
|
-
httpsAgent: new https.Agent({
|
|
168
|
-
rejectUnauthorized: false, // Allow self-signed certs in dev
|
|
169
|
-
}),
|
|
170
|
-
validateStatus: (status) => status < 500,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
if (runResponse.status !== 200 && runResponse.status !== 201) {
|
|
174
|
-
throw new Error(`Run creation failed: ${runResponse.status} - ${runResponse.data?.message || 'Unknown error'}`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const runId = runResponse.data.run_id;
|
|
178
|
-
|
|
179
|
-
// STEP 2: Get browser session JWT token
|
|
180
|
-
const jwtEndpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/auth/jwt/browser`;
|
|
181
|
-
|
|
182
|
-
const jwtPayload = {
|
|
183
|
-
api_key: sdkConfig.apiKey
|
|
184
|
-
};
|
|
185
|
-
|
|
186
172
|
const jwtResponse = await axios.post(jwtEndpoint, jwtPayload, {
|
|
187
173
|
headers: {
|
|
188
174
|
'Content-Type': 'application/json',
|
|
@@ -199,7 +185,7 @@ async function getJWTToken(sdkConfig) {
|
|
|
199
185
|
|
|
200
186
|
return {
|
|
201
187
|
jwt_token: jwtResponse.data.jwt_token,
|
|
202
|
-
run_id:
|
|
188
|
+
run_id: null, // No run created during auth setup
|
|
203
189
|
user_id: jwtResponse.data.user_id,
|
|
204
190
|
project_id: jwtResponse.data.project_id,
|
|
205
191
|
expires_in: jwtResponse.data.expires_in,
|
|
@@ -3,6 +3,7 @@ const UuidExtractor = require('./helpers/UuidExtractor');
|
|
|
3
3
|
const DeviceOsDetector = require('./helpers/DeviceOsDetector');
|
|
4
4
|
const PayloadBuilder = require('../../utils/PayloadBuilder');
|
|
5
5
|
const logger = require('../../utils/logger');
|
|
6
|
+
const { DEFAULT_APPLIQATION_BASE_URL } = require('../../constants');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Appliqation Reporter for Playwright
|
|
@@ -51,6 +52,25 @@ class AppliqationReporter {
|
|
|
51
52
|
...config
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
// Apply baseUrl fallback if not provided
|
|
56
|
+
// Priority: 1) config.baseUrl, 2) process.env.APPLIQATION_BASE_URL, 3) DEFAULT_APPLIQATION_BASE_URL
|
|
57
|
+
if (!this.config.baseUrl) {
|
|
58
|
+
this.config.baseUrl = process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Apply environment from process.env if not in config
|
|
62
|
+
// Priority: 1) config.environment, 2) process.env.APPLIQATION_ENVIRONMENT
|
|
63
|
+
if (!this.config.environment) {
|
|
64
|
+
this.config.environment = process.env.APPLIQATION_ENVIRONMENT;
|
|
65
|
+
}
|
|
66
|
+
// Note: No default fallback - validation will catch missing value
|
|
67
|
+
|
|
68
|
+
// Apply title fallback if not provided
|
|
69
|
+
// Priority: 1) config.title, 2) process.env.APPLIQATION_RUN_TITLE, 3) timestamp-based default
|
|
70
|
+
if (!this.config.title) {
|
|
71
|
+
this.config.title = process.env.APPLIQATION_RUN_TITLE || `Automation Run - ${new Date().toISOString()}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
// Validate configuration
|
|
55
75
|
this.validateConfig();
|
|
56
76
|
|
|
@@ -77,11 +97,14 @@ class AppliqationReporter {
|
|
|
77
97
|
this.skippedTests = 0;
|
|
78
98
|
this.orphanTests = 0;
|
|
79
99
|
|
|
100
|
+
// Show baseUrl only in DEBUG mode
|
|
80
101
|
logger.info('Appliqation Playwright Reporter initialized', {
|
|
81
|
-
baseUrl: this.config.baseUrl,
|
|
82
|
-
scenarioId: this.config.scenarioId,
|
|
83
102
|
environment: this.config.environment
|
|
84
103
|
});
|
|
104
|
+
logger.debug('Reporter configuration', {
|
|
105
|
+
baseUrl: this.config.baseUrl,
|
|
106
|
+
scenarioId: this.config.scenarioId
|
|
107
|
+
});
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
/**
|
|
@@ -135,12 +158,13 @@ class AppliqationReporter {
|
|
|
135
158
|
logger.info(`Creating ${matrixConfigs.length} run matrices for device/OS combinations...`);
|
|
136
159
|
logger.debug(`Matrix configurations:`, matrixConfigs);
|
|
137
160
|
|
|
138
|
-
// Create runs for each device/OS combination
|
|
139
|
-
|
|
161
|
+
// Create runs for each device/OS combination in parallel for better performance
|
|
162
|
+
const creationStartTime = Date.now();
|
|
163
|
+
const creationPromises = matrixConfigs.map(async (matrixConfig) => {
|
|
140
164
|
const projectKey = `${matrixConfig.device}-${matrixConfig.os}`;
|
|
141
165
|
|
|
142
166
|
// Skip if already created (shouldn't happen but safety check)
|
|
143
|
-
if (this.runsByProject.has(projectKey))
|
|
167
|
+
if (this.runsByProject.has(projectKey)) return null;
|
|
144
168
|
|
|
145
169
|
try {
|
|
146
170
|
const runOptions = {
|
|
@@ -170,6 +194,8 @@ class AppliqationReporter {
|
|
|
170
194
|
runId: run.runId,
|
|
171
195
|
browsers: matrixConfig.browsers
|
|
172
196
|
});
|
|
197
|
+
|
|
198
|
+
return { projectKey, runId: run.runId };
|
|
173
199
|
} catch (error) {
|
|
174
200
|
// Check if this is an API validation error with details
|
|
175
201
|
if (error.details) {
|
|
@@ -226,7 +252,55 @@ class AppliqationReporter {
|
|
|
226
252
|
throw error;
|
|
227
253
|
}
|
|
228
254
|
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Wait for all run creations to complete in parallel
|
|
258
|
+
await Promise.all(creationPromises);
|
|
259
|
+
|
|
260
|
+
const creationDuration = Date.now() - creationStartTime;
|
|
261
|
+
logger.info(`All run matrices created in ${creationDuration}ms`);
|
|
262
|
+
console.log(`✅ Created ${this.runsByProject.size} run matrix(es) in ${(creationDuration / 1000).toFixed(2)}s`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Wait for run matrix to be available (handles race condition with slow backend)
|
|
267
|
+
* @param {string} projectKey - The project key to wait for
|
|
268
|
+
* @param {number} maxWaitMs - Maximum time to wait in milliseconds (default 60s for slow backends)
|
|
269
|
+
* @returns {Promise<Object|null>} Run info object or null if timeout
|
|
270
|
+
*/
|
|
271
|
+
async waitForRunMatrix(projectKey, maxWaitMs = 60000) {
|
|
272
|
+
const startTime = Date.now();
|
|
273
|
+
const pollInterval = 500; // Check every 500ms
|
|
274
|
+
let warningShown = false;
|
|
275
|
+
|
|
276
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
277
|
+
const runInfo = this.runsByProject.get(projectKey);
|
|
278
|
+
|
|
279
|
+
if (runInfo) {
|
|
280
|
+
const waitedMs = Date.now() - startTime;
|
|
281
|
+
if (waitedMs > 1000) {
|
|
282
|
+
logger.info(`Run matrix became available after ${waitedMs}ms`, { projectKey });
|
|
283
|
+
}
|
|
284
|
+
return runInfo;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Show warning if waiting too long (5 seconds)
|
|
288
|
+
if (!warningShown && Date.now() - startTime > 5000) {
|
|
289
|
+
console.warn(`⏳ Waiting for run matrix creation for ${projectKey}... (this may take a moment with slow backends)`);
|
|
290
|
+
logger.warn(`Still waiting for run matrix after 5 seconds`, { projectKey });
|
|
291
|
+
warningShown = true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Wait before next poll
|
|
295
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
229
296
|
}
|
|
297
|
+
|
|
298
|
+
// Timeout reached
|
|
299
|
+
logger.error(`Timeout waiting for run matrix after ${maxWaitMs}ms`, { projectKey });
|
|
300
|
+
console.error(`\n❌ TIMEOUT: Run matrix creation took longer than ${maxWaitMs / 1000}s for ${projectKey}`);
|
|
301
|
+
console.error(` This usually indicates slow backend response or browser version detection delays.`);
|
|
302
|
+
console.error(` Try adding browserVersion to your project metadata to speed up run creation.\n`);
|
|
303
|
+
return null;
|
|
230
304
|
}
|
|
231
305
|
|
|
232
306
|
/**
|
|
@@ -256,11 +330,16 @@ class AppliqationReporter {
|
|
|
256
330
|
const deviceInfo = DeviceOsDetector.getDeviceInfo(project, null);
|
|
257
331
|
const projectKey = `${deviceInfo.device}-${deviceInfo.os}`;
|
|
258
332
|
|
|
259
|
-
//
|
|
260
|
-
const runInfo = this.
|
|
333
|
+
// Wait for run matrix to be available (handles race condition with slow backend)
|
|
334
|
+
const runInfo = await this.waitForRunMatrix(projectKey, 30000);
|
|
261
335
|
|
|
262
336
|
if (!runInfo) {
|
|
263
|
-
logger.
|
|
337
|
+
logger.error('Run matrix not available after timeout - result will not be submitted', {
|
|
338
|
+
projectKey,
|
|
339
|
+
testTitle: test.title,
|
|
340
|
+
testFile: test.location?.file
|
|
341
|
+
});
|
|
342
|
+
console.error(`\n⚠️ ERROR: Run matrix not created for ${projectKey}. Test result for "${test.title}" will not be submitted.\n`);
|
|
264
343
|
return;
|
|
265
344
|
}
|
|
266
345
|
|
|
@@ -535,6 +614,7 @@ class AppliqationReporter {
|
|
|
535
614
|
|
|
536
615
|
/**
|
|
537
616
|
* Auto-detect and inject browser versions for projects missing browserVersion metadata
|
|
617
|
+
* Now runs in parallel for better performance
|
|
538
618
|
* @private
|
|
539
619
|
* @param {Array} projects - Array of Playwright project configurations
|
|
540
620
|
*/
|
|
@@ -555,13 +635,17 @@ class AppliqationReporter {
|
|
|
555
635
|
|
|
556
636
|
const { chromium, firefox, webkit } = playwright;
|
|
557
637
|
|
|
558
|
-
for
|
|
638
|
+
// Cache for browser versions to avoid launching same browser multiple times
|
|
639
|
+
const versionCache = new Map();
|
|
640
|
+
|
|
641
|
+
// Create detection promises for all projects in parallel
|
|
642
|
+
const detectionPromises = projects.map(async (project) => {
|
|
559
643
|
// Skip if browserVersion is already configured
|
|
560
644
|
if (project.use?.metadata?.browserVersion) {
|
|
561
645
|
logger.debug(`Project "${project.name}" already has browserVersion configured`, {
|
|
562
646
|
version: project.use.metadata.browserVersion
|
|
563
647
|
});
|
|
564
|
-
|
|
648
|
+
return null;
|
|
565
649
|
}
|
|
566
650
|
|
|
567
651
|
// Get browser name from project - check multiple sources
|
|
@@ -579,33 +663,45 @@ class AppliqationReporter {
|
|
|
579
663
|
}
|
|
580
664
|
}
|
|
581
665
|
|
|
582
|
-
if (!browserName)
|
|
666
|
+
if (!browserName) return null;
|
|
583
667
|
|
|
584
668
|
try {
|
|
585
|
-
let browser = null;
|
|
586
669
|
let version = null;
|
|
587
670
|
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
671
|
+
// Check cache first
|
|
672
|
+
if (versionCache.has(browserName)) {
|
|
673
|
+
version = versionCache.get(browserName);
|
|
674
|
+
logger.debug(`Using cached version for ${browserName}`, { version });
|
|
675
|
+
} else {
|
|
676
|
+
// Launch browser to detect version
|
|
677
|
+
let browser = null;
|
|
678
|
+
|
|
679
|
+
switch (browserName.toLowerCase()) {
|
|
680
|
+
case 'chromium':
|
|
681
|
+
case 'chrome':
|
|
682
|
+
browser = await chromium.launch({ headless: true });
|
|
683
|
+
version = browser.version();
|
|
684
|
+
await browser.close();
|
|
685
|
+
break;
|
|
686
|
+
|
|
687
|
+
case 'firefox':
|
|
688
|
+
browser = await firefox.launch({ headless: true });
|
|
689
|
+
version = browser.version();
|
|
690
|
+
await browser.close();
|
|
691
|
+
break;
|
|
692
|
+
|
|
693
|
+
case 'webkit':
|
|
694
|
+
case 'safari':
|
|
695
|
+
browser = await webkit.launch({ headless: true });
|
|
696
|
+
version = browser.version();
|
|
697
|
+
await browser.close();
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
602
700
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
await browser.close();
|
|
608
|
-
break;
|
|
701
|
+
// Cache the version
|
|
702
|
+
if (version) {
|
|
703
|
+
versionCache.set(browserName, version);
|
|
704
|
+
}
|
|
609
705
|
}
|
|
610
706
|
|
|
611
707
|
if (version) {
|
|
@@ -624,6 +720,8 @@ class AppliqationReporter {
|
|
|
624
720
|
version: majorVersion,
|
|
625
721
|
fullVersion: version
|
|
626
722
|
});
|
|
723
|
+
|
|
724
|
+
return { project: project.name, browser: browserName, version: majorVersion };
|
|
627
725
|
}
|
|
628
726
|
}
|
|
629
727
|
} catch (error) {
|
|
@@ -632,6 +730,17 @@ class AppliqationReporter {
|
|
|
632
730
|
error: error.message
|
|
633
731
|
});
|
|
634
732
|
}
|
|
733
|
+
|
|
734
|
+
return null;
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// Wait for all browser version detections to complete in parallel
|
|
738
|
+
const results = await Promise.all(detectionPromises);
|
|
739
|
+
|
|
740
|
+
// Log summary
|
|
741
|
+
const detected = results.filter(r => r !== null);
|
|
742
|
+
if (detected.length > 0) {
|
|
743
|
+
logger.info(`Browser version detection completed for ${detected.length} project(s)`);
|
|
635
744
|
}
|
|
636
745
|
}
|
|
637
746
|
|
|
@@ -652,8 +761,25 @@ class AppliqationReporter {
|
|
|
652
761
|
throw new Error('projectKey is required');
|
|
653
762
|
}
|
|
654
763
|
|
|
655
|
-
//
|
|
656
|
-
|
|
764
|
+
// Environment is required - no default fallback
|
|
765
|
+
if (!this.config.environment) {
|
|
766
|
+
throw new Error(
|
|
767
|
+
'\n' +
|
|
768
|
+
'❌ Testing environment is required.\n' +
|
|
769
|
+
'\n' +
|
|
770
|
+
'Please provide the environment in your test run command:\n' +
|
|
771
|
+
'\n' +
|
|
772
|
+
' APPLIQATION_ENVIRONMENT=Production npx playwright test\n' +
|
|
773
|
+
'\n' +
|
|
774
|
+
'Or set it in your .env file:\n' +
|
|
775
|
+
'\n' +
|
|
776
|
+
' APPLIQATION_ENVIRONMENT=Production\n' +
|
|
777
|
+
'\n' +
|
|
778
|
+
'Valid environments are configured in your Appliqation project settings.\n'
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Note: scenarioId, testSetId, and title are optional
|
|
657
783
|
}
|
|
658
784
|
|
|
659
785
|
/**
|