@appliqation/automation-sdk 2.4.0 → 2.5.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.
@@ -306,68 +306,6 @@ class HttpClient {
306
306
  }
307
307
  }
308
308
 
309
- /**
310
- * Fetch project information from API key
311
- * Enables SDK auto-configuration where only API key is required.
312
- * Returns project metadata including ID, title, and configured environments.
313
- *
314
- * @returns {Promise<Object>} Project metadata
315
- * @property {boolean} success - Operation success status
316
- * @property {number} project_id - Numeric project ID
317
- * @property {number} project_nid - Same as project_id (for consistency)
318
- * @property {string} project_title - Project name
319
- * @property {Array<string>} environments - List of configured environment names
320
- * @property {string} default_environment - First environment or 'Local' fallback
321
- * @property {boolean} has_environments_configured - Whether environments exist
322
- *
323
- * @throws {Error} If API key is invalid or project not found
324
- *
325
- * @example
326
- * const projectInfo = await httpClient.getProjectInfo();
327
- * console.log(projectInfo.project_id); // 1162
328
- * console.log(projectInfo.environments); // ['Development', 'Staging', 'Production']
329
- * console.log(projectInfo.default_environment); // 'Development'
330
- */
331
- async getProjectInfo() {
332
- logger.debug('Fetching project information from API key...');
333
-
334
- try {
335
- const response = await this.get('/api/automation/project/info');
336
-
337
- logger.info('Project info retrieved successfully', {
338
- project_id: response.data.project_id,
339
- project_title: response.data.project_title,
340
- environments: response.data.environments,
341
- has_environments: response.data.has_environments_configured
342
- });
343
-
344
- return response.data;
345
- } catch (error) {
346
- logger.error('Failed to fetch project info', {
347
- error: error.message,
348
- status: error.response?.status,
349
- code: error.code
350
- });
351
-
352
- // Enhance error message for common scenarios
353
- if (error.response?.status === 400) {
354
- throw new Error(
355
- 'No project associated with this API key. Please verify your API key is correctly configured.'
356
- );
357
- } else if (error.response?.status === 404) {
358
- throw new Error(
359
- 'Project not found or invalid. Please verify your API key is associated with a valid project.'
360
- );
361
- } else if (error.response?.status === 401 || error.response?.status === 403) {
362
- throw new Error(
363
- 'API key authentication failed. Please verify your API key is valid and not expired.'
364
- );
365
- }
366
-
367
- throw error;
368
- }
369
- }
370
-
371
309
  /**
372
310
  * Set authorization header
373
311
  * @param {string} token - Authorization token
@@ -1,170 +1,93 @@
1
1
  /**
2
2
  * Global Setup for JWT Browser Authentication
3
3
  *
4
- * This module automatically sets up browser authentication using JWT tokens
5
- * instead of traditional username/password login (setup-auth.js).
6
- *
7
- * How it works:
8
- * 1. Uses API Key to call /api/automation/run/create
9
- * 2. Receives JWT token from backend
10
- * 3. Navigates browser to app with ?qonsole_token={jwt}
11
- * 4. Backend validates JWT and establishes session
12
- * 5. Saves authenticated state to .auth/jwt.json
13
- * 6. All tests use this authenticated state
14
- *
15
- * Benefits:
16
- * - No username/password in .env
17
- * - No manual setup step required
18
- * - 1-hour token validity
19
- * - Auto-refresh support
20
- * - Works in CI/CD environments
4
+ * Enabled only when --appq CLI flag is present.
5
+ * Never blocks Playwright test execution — all errors degrade to warnings.
21
6
  *
22
7
  * @module playwright/global-setup
23
8
  */
24
9
 
25
- const { chromium } = require('@playwright/test');
10
+ // Lazy-require @playwright/test to avoid double-load when SDK is symlinked
26
11
  const axios = require('axios');
27
12
  const https = require('https');
28
13
  const fs = require('fs');
29
14
  const path = require('path');
30
15
  const { DEFAULT_APPLIQATION_BASE_URL } = require('../constants');
31
16
 
32
- function toBoolean(value) {
33
- if (value === undefined || value === null) return undefined;
34
- if (typeof value === 'boolean') return value;
35
- const normalized = String(value).trim().toLowerCase();
36
- if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
37
- if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
38
- return undefined;
39
- }
40
-
41
- function parseCliEnableFlag() {
42
- const argv = process.argv || [];
43
- for (let i = 0; i < argv.length; i++) {
44
- const arg = argv[i];
45
- if (arg.startsWith('--appq=')) {
46
- return toBoolean(arg.split('=')[1]);
47
- }
48
- if (arg === '--appq') {
49
- return true;
50
- }
51
- if (arg.startsWith('--enable-appq=')) {
52
- return toBoolean(arg.split('=')[1]);
53
- }
54
- if (arg === '--enable-appq') {
55
- return true;
56
- }
57
- }
58
- return undefined;
59
- }
60
-
61
- function resolveEnableAppq(config) {
62
- const cliValue = parseCliEnableFlag();
63
- const envValue = process.env.APPLIQATION_ENABLE ?? process.env.APPQ ?? process.env.APPLIQATION_APPQ;
64
- const configValue = config?.enableAppq ?? config?.options?.enableAppq;
65
- const resolved = toBoolean(configValue) ?? toBoolean(cliValue) ?? toBoolean(envValue);
66
- return resolved !== undefined ? resolved : true;
17
+ /**
18
+ * Check if --appq flag is present in CLI args
19
+ * @returns {boolean}
20
+ */
21
+ function isAppqEnabled() {
22
+ return (process.argv || []).includes('--appq');
67
23
  }
68
24
 
69
25
  /**
70
26
  * Setup JWT-based browser authentication
71
- *
72
27
  * @param {Object} config - Playwright config object
73
28
  * @returns {Promise<void>}
74
29
  */
75
30
  async function globalSetup(config) {
76
- console.log('\n🚀 Global Setup: Initializing Test Environment...\n');
77
-
78
- // Extract SDK configuration from reporter config or use env
79
- const sdkConfig = extractSDKConfig(config);
80
- sdkConfig.enableAppq = resolveEnableAppq(sdkConfig);
81
-
82
- // Display startup information
83
- console.log(`🌍 Environment: ${sdkConfig.environment || 'Not specified'}`);
84
- console.log(`🔗 Test App URL: ${sdkConfig.appUrl || 'Not specified'}`);
31
+ console.log('\n[Appliqation] Global Setup: Initializing...\n');
85
32
 
86
- if (sdkConfig.enableAppq === false) {
87
- console.log('⚠️ Appq disabled via flag. Skipping JWT setup and backend calls.\n');
33
+ // Only run if --appq flag is present
34
+ if (!isAppqEnabled()) {
35
+ console.log('[Appliqation] Reporting disabled. Skipping JWT setup.\n');
88
36
  return;
89
37
  }
90
38
 
91
- // Show Run ID and Timestamp only in DEBUG mode
92
- if (process.env.LOG_LEVEL === 'DEBUG') {
93
- const crypto = require('crypto');
94
- const runId = crypto.randomUUID();
95
- console.log(`📊 Run ID: ${runId}`);
96
- console.log(`⏰ Timestamp: ${new Date().toISOString()}`);
97
- }
98
-
99
- console.log(`🔑 API Key: ${sdkConfig.apiKey ? 'Configured' : 'Not set'}\n`);
39
+ const sdkConfig = extractSDKConfig(config);
100
40
 
101
- console.log('🔐 Setting up JWT browser authentication...\n');
41
+ console.log(`[Appliqation] Environment: ${sdkConfig.environment || 'Not specified'}`);
102
42
 
103
- // Validate required configuration
43
+ // Validate API key — warn, don't throw
104
44
  if (!sdkConfig.apiKey) {
105
- console.error(' APPLIQATION_API_KEY is required for JWT authentication');
106
- console.error(' Please add to your .env file:\n');
107
- console.error(' APPLIQATION_API_KEY=appq_live_xxxxx\n');
108
- throw new Error('Missing APPLIQATION_API_KEY');
45
+ console.warn('[Appliqation] API key required. Set APPLIQATION_API_KEY in .env');
46
+ console.warn('[Appliqation] Tests will continue without JWT auth.\n');
47
+ return;
109
48
  }
110
49
 
111
- if (!sdkConfig.baseUrl) {
112
- console.error('❌ APPLIQATION_BASE_URL is required');
113
- throw new Error('Missing APPLIQATION_BASE_URL');
114
- }
50
+ console.log('[Appliqation] API Key: Configured');
115
51
 
116
52
  // Determine if JWT auth is enabled
117
- const useJwtAuth = sdkConfig.useJwtAuth !== false; // Default: true
53
+ const useJwtAuth = sdkConfig.useJwtAuth !== false;
118
54
 
119
55
  if (!useJwtAuth) {
120
- console.log('ℹ️ JWT authentication disabled. Using legacy session auth.\n');
56
+ console.log('[Appliqation] JWT authentication disabled.\n');
121
57
  return;
122
58
  }
123
59
 
124
60
  try {
125
- // Step 1: Get JWT token from API
126
- console.log('📡 Requesting JWT token from Appliqation backend...');
61
+ console.log('[Appliqation] Requesting JWT token...');
127
62
  const jwtData = await getJWTToken(sdkConfig);
128
63
 
129
64
  if (!jwtData.jwt_token) {
130
- console.warn('⚠️ No JWT token received. Falling back to legacy auth.');
65
+ console.warn('[Appliqation] No JWT token received. Tests will continue without auth.\n');
131
66
  return;
132
67
  }
133
68
 
134
- console.log(`✅ JWT token received (expires in 1 hour)\n`);
69
+ console.log('[Appliqation] JWT token received.');
135
70
 
136
- // Step 2: Setup browser with JWT
137
- console.log('🌐 Setting up browser session with JWT...');
71
+ console.log('[Appliqation] Setting up browser session...');
138
72
  await setupBrowserWithJWT(sdkConfig, jwtData.jwt_token);
139
73
 
140
- console.log(' Browser authentication setup complete!');
141
- console.log('📄 Authenticated state saved to: .auth/jwt.json\n');
142
- console.log('✨ Tests will run with JWT authentication!\n');
74
+ console.log('[Appliqation] Browser authentication complete.');
75
+ console.log('[Appliqation] State saved to .auth/jwt.json\n');
143
76
 
144
- // Store for use in tests (run_id will be set by reporter when tests execute)
145
77
  process.env.APPLIQATION_JWT_TOKEN = jwtData.jwt_token;
146
78
 
147
79
  } catch (error) {
148
- console.error('\n❌ JWT authentication setup failed:', error.message);
149
- console.error('\n💡 Troubleshooting:');
150
- console.error(' 1. Check APPLIQATION_API_KEY is valid');
151
- console.error(' 2. Check APPLIQATION_BASE_URL is accessible');
152
- console.error(' 3. Check backend has QONSOLE_JWT_SECRET configured');
153
- console.error(' 4. Try running: npm run setup-auth (legacy method)\n');
154
-
155
- // Don't fail the test run - fall back to legacy auth
156
- console.warn('⚠️ Falling back to legacy authentication method\n');
80
+ console.warn(`[Appliqation] Auth setup failed: ${error.message}`);
81
+ console.warn('[Appliqation] Tests will continue without JWT auth.\n');
157
82
  }
158
83
  }
159
84
 
160
85
  /**
161
86
  * Extract SDK configuration from Playwright config
162
- *
163
87
  * @param {Object} config - Playwright config
164
88
  * @returns {Object} SDK configuration
165
89
  */
166
90
  function extractSDKConfig(config) {
167
- // Try to find SDK config in reporter configuration
168
91
  let sdkConfig = {};
169
92
 
170
93
  if (config.reporter) {
@@ -181,15 +104,14 @@ function extractSDKConfig(config) {
181
104
  }
182
105
  }
183
106
 
184
- // Fallback to environment variables
185
- const baseUrl = sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL;
107
+ const baseUrl = sdkConfig.baseUrl || DEFAULT_APPLIQATION_BASE_URL;
186
108
 
187
109
  return {
188
110
  baseUrl,
189
111
  apiKey: sdkConfig.apiKey || process.env.APPLIQATION_API_KEY,
190
112
  projectKey: sdkConfig.projectKey || process.env.APPLIQATION_PROJECT_KEY,
191
113
  scenarioId: sdkConfig.scenarioId || parseInt(process.env.APPLIQATION_SCENARIO_ID) || 0,
192
- environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT || 'Local',
114
+ environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT,
193
115
  appUrl: sdkConfig.appUrl || process.env.APPLIQATION_APP_URL || process.env.TEST_APP_URL || baseUrl,
194
116
  useJwtAuth: sdkConfig.useJwtAuth !== undefined ? sdkConfig.useJwtAuth : process.env.APPLIQATION_USE_JWT_AUTH !== 'false',
195
117
  runTitle: sdkConfig.runTitle || process.env.APPLIQATION_RUN_TITLE || `JWT Auth Setup - ${new Date().toISOString()}`,
@@ -198,13 +120,10 @@ function extractSDKConfig(config) {
198
120
 
199
121
  /**
200
122
  * Get JWT token from Appliqation API
201
- *
202
123
  * @param {Object} sdkConfig - SDK configuration
203
- * @returns {Promise<Object>} JWT data { jwt_token, run_id, user_id, project_id }
124
+ * @returns {Promise<Object>} JWT data
204
125
  */
205
126
  async function getJWTToken(sdkConfig) {
206
- // Get browser session JWT token directly (no need to create run matrix)
207
- // The SDK reporter will create run matrices automatically when tests execute
208
127
  const jwtEndpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/auth/jwt/browser`;
209
128
 
210
129
  const jwtPayload = {
@@ -213,12 +132,8 @@ async function getJWTToken(sdkConfig) {
213
132
 
214
133
  try {
215
134
  const jwtResponse = await axios.post(jwtEndpoint, jwtPayload, {
216
- headers: {
217
- 'Content-Type': 'application/json',
218
- },
219
- httpsAgent: new https.Agent({
220
- rejectUnauthorized: false, // Allow self-signed certs in dev
221
- }),
135
+ headers: { 'Content-Type': 'application/json' },
136
+ httpsAgent: new https.Agent({ rejectUnauthorized: false }),
222
137
  validateStatus: (status) => status < 500,
223
138
  });
224
139
 
@@ -228,12 +143,11 @@ async function getJWTToken(sdkConfig) {
228
143
 
229
144
  return {
230
145
  jwt_token: jwtResponse.data.jwt_token,
231
- run_id: null, // No run created during auth setup
146
+ run_id: null,
232
147
  user_id: jwtResponse.data.user_id,
233
148
  project_id: jwtResponse.data.project_id,
234
149
  expires_in: jwtResponse.data.expires_in,
235
150
  };
236
-
237
151
  } catch (error) {
238
152
  if (error.response) {
239
153
  throw new Error(`API error: ${error.response.status} - ${error.response.data?.message || error.message}`);
@@ -244,69 +158,46 @@ async function getJWTToken(sdkConfig) {
244
158
 
245
159
  /**
246
160
  * Setup browser session with JWT token
247
- *
248
- * @param {Object} sdkConfig - SDK configuration
249
- * @param {string} jwtToken - JWT token
250
- * @returns {Promise<void>}
161
+ * @param {Object} sdkConfig
162
+ * @param {string} jwtToken
251
163
  */
252
164
  async function setupBrowserWithJWT(sdkConfig, jwtToken) {
253
- const browser = await chromium.launch({
254
- headless: true,
255
- });
165
+ const { chromium } = require('@playwright/test');
166
+ const browser = await chromium.launch({ headless: true });
256
167
 
257
168
  try {
258
- // Extract domain from baseUrl for cookie
259
169
  const baseUrl = sdkConfig.baseUrl.replace(/\/$/, '');
260
170
  const urlObj = new URL(baseUrl);
261
171
  const domain = urlObj.hostname;
262
172
 
263
- // Create context with JWT cookie BEFORE navigating
264
- const context = await browser.newContext({
265
- ignoreHTTPSErrors: true, // Allow self-signed certs in dev
266
- });
173
+ const context = await browser.newContext({ ignoreHTTPSErrors: true });
267
174
 
268
- // Set JWT cookie
269
175
  await context.addCookies([{
270
176
  name: 'appliqation_jwt',
271
177
  value: jwtToken,
272
- domain: domain,
178
+ domain,
273
179
  path: '/',
274
180
  httpOnly: true,
275
181
  secure: baseUrl.startsWith('https'),
276
182
  sameSite: 'Lax'
277
183
  }]);
278
184
 
279
- console.log(` ✓ JWT cookie set for domain: ${domain}`);
280
-
281
185
  const page = await context.newPage();
282
186
 
283
- // Navigate to app (no query parameter needed)
284
- console.log(` Navigating to: ${baseUrl}...`);
285
- await page.goto(baseUrl, {
286
- waitUntil: 'networkidle',
287
- timeout: 30000,
288
- });
289
-
290
- // Wait for authentication to be processed
187
+ await page.goto(baseUrl, { waitUntil: 'networkidle', timeout: 30000 });
291
188
  await page.waitForTimeout(2000);
292
189
 
293
- // Verify authentication worked
294
190
  const currentUrl = page.url();
295
191
  if (currentUrl.includes('/user/login')) {
296
- throw new Error('JWT authentication failed - redirected to login page');
192
+ throw new Error('JWT authentication failed redirected to login page');
297
193
  }
298
194
 
299
- console.log(' ✓ Browser authenticated successfully');
300
-
301
- // Save authenticated state with cookie
302
195
  const authDir = path.resolve('.auth');
303
196
  if (!fs.existsSync(authDir)) {
304
197
  fs.mkdirSync(authDir, { recursive: true });
305
198
  }
306
199
 
307
200
  await context.storageState({ path: '.auth/jwt.json' });
308
- console.log(' ✓ Authenticated state saved to .auth/jwt.json');
309
-
310
201
  } catch (error) {
311
202
  throw new Error(`Browser setup failed: ${error.message}`);
312
203
  } finally {
@@ -161,18 +161,6 @@ class CypressReporter {
161
161
  */
162
162
  async createRun(details) {
163
163
  try {
164
- // Auto-configure project from API key if needed
165
- await this.client.ensureProjectConfig();
166
-
167
- // Use auto-detected environment if not explicitly provided
168
- if (!this.config.environment && this.client._projectInfo) {
169
- this.config.environment = this.client._projectInfo.default_environment;
170
- logger.info('Using auto-detected default environment', {
171
- environment: this.config.environment,
172
- source: 'api_key_project_config'
173
- });
174
- }
175
-
176
164
  const browser = details.browser?.displayName || details.browser?.name || 'Cypress';
177
165
  const platform = details.system?.platform || process.platform;
178
166
  const os = this.detectOS(platform);
@@ -137,18 +137,6 @@ class JestReporter {
137
137
  */
138
138
  async createRun() {
139
139
  try {
140
- // Auto-configure project from API key if needed
141
- await this.client.ensureProjectConfig();
142
-
143
- // Use auto-detected environment if not explicitly provided
144
- if (!this.config.environment && this.client._projectInfo) {
145
- this.config.environment = this.client._projectInfo.default_environment;
146
- logger.info('Using auto-detected default environment', {
147
- environment: this.config.environment,
148
- source: 'api_key_project_config'
149
- });
150
- }
151
-
152
140
  const os = this.detectOS();
153
141
 
154
142
  const runOptions = {