@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 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
- APPLIQATION_ENVIRONMENT=Your-test-environment-name
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
- - `APPLIQATION_ENVIRONMENT` - Environment name (Local, Staging, Production, etc. Found in your project settings)
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.APPLIQATION_BASE_URL,
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
- │ ├─ APPLIQATION_BASE_URL
261
- │ └─ APPLIQATION_SCENARIO_ID
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
- │ ├── APPLIQATION_BASE_URL=https://your-domain.com
412
- ├── APPLIQATION_SCENARIO_ID=1154
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
- | `APPLIQATION_BASE_URL` | ✅ Yes | Your Appliqation portal URL (no trailing slash) | `https://portal.appliqation.com` |
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 `APPLIQATION_BASE_URL` (no trailing slash):
568
+ 4. Check `.env` has correct `TEST_APP_URL` (no trailing slash):
602
569
  ```bash
603
570
  # ❌ Wrong
604
- APPLIQATION_BASE_URL=https://portal.com/
571
+ TEST_APP_URL=https://portal.com/
605
572
 
606
573
  # ✅ Correct
607
- APPLIQATION_BASE_URL=https://portal.com
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. **Wrong scenario ID** - Results sent to different scenario
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
- 4. **Network issues** - Portal unreachable
699
- - **Fix:** Check `APPLIQATION_BASE_URL` is accessible
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.7",
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",
@@ -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(config);
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 (config.rejectUnauthorized !== undefined) {
69
+ if (normalizedConfig.rejectUnauthorized !== undefined) {
62
70
  // User explicitly set rejectUnauthorized
63
- if (isProduction && config.rejectUnauthorized === false) {
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 = config.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: config.baseUrl.replace(/\/$/, ''), // Remove trailing slash
84
- apiKey: config.apiKey,
85
- projectKey: config.projectKey,
86
- username: config.username,
87
- password: config.password,
88
- runTitle: config.runTitle || config.title || process.env.APPLIQATION_RUN_TITLE || null,
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: config.options?.timeout || 30000,
92
- retries: config.options?.retries || 3,
93
- logOrphans: config.options?.logOrphans !== false,
94
- logLevel: config.options?.logLevel || 'info'
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
- logger.info('Appliqation client initialized', {
114
- baseUrl: this.config.baseUrl,
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🔐 Setting up JWT browser authentication...\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: sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL,
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 || sdkConfig.baseUrl || process.env.APPLIQATION_BASE_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
- // STEP 1: Create automation run
147
- const createRunEndpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/automation/run/create`;
148
-
149
- const runPayload = {
150
- project_key: sdkConfig.projectKey,
151
- scenario_id: sdkConfig.scenarioId || 0,
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: runId,
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 with ALL browsers
139
- for (const matrixConfig of matrixConfigs) {
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)) continue;
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
- // Get run info for this device/OS combination (pre-created in onBegin)
260
- const runInfo = this.runsByProject.get(projectKey);
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.warn('No run matrix found for project', { projectKey });
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 (const project of projects) {
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
- continue;
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) continue;
666
+ if (!browserName) return null;
583
667
 
584
668
  try {
585
- let browser = null;
586
669
  let version = null;
587
670
 
588
- // Launch browser to detect version
589
- switch (browserName.toLowerCase()) {
590
- case 'chromium':
591
- case 'chrome':
592
- browser = await chromium.launch({ headless: true });
593
- version = browser.version();
594
- await browser.close();
595
- break;
596
-
597
- case 'firefox':
598
- browser = await firefox.launch({ headless: true });
599
- version = browser.version();
600
- await browser.close();
601
- break;
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
- case 'webkit':
604
- case 'safari':
605
- browser = await webkit.launch({ headless: true });
606
- version = browser.version();
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
- // Note: scenarioId and testSetId are now optional
656
- // If neither is provided, will default to 0 for generic automation runs
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
  /**