@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.
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/package.json +107 -0
- package/src/AppliqationClient.js +562 -0
- package/src/constants.js +245 -0
- package/src/core/AuthManager.js +353 -0
- package/src/core/HttpClient.js +475 -0
- package/src/index.d.ts +333 -0
- package/src/index.js +26 -0
- package/src/playwright/JwtBrowserAuth.js +240 -0
- package/src/playwright/fixture.js +92 -0
- package/src/playwright/global-setup.js +243 -0
- package/src/playwright/helpers/jwt-browser-auth.js +227 -0
- package/src/playwright/index.js +16 -0
- package/src/reporters/cypress/CypressReporter.js +387 -0
- package/src/reporters/cypress/UuidExtractor.js +139 -0
- package/src/reporters/cypress/index.js +30 -0
- package/src/reporters/jest/JestReporter.js +361 -0
- package/src/reporters/jest/UuidExtractor.js +174 -0
- package/src/reporters/jest/index.js +28 -0
- package/src/reporters/playwright/AppliqationReporter.js +654 -0
- package/src/reporters/playwright/helpers/DeviceOsDetector.js +435 -0
- package/src/reporters/playwright/helpers/UuidExtractor.js +290 -0
- package/src/reporters/playwright/index.d.ts +96 -0
- package/src/reporters/playwright/index.js +14 -0
- package/src/services/OrphanTestService.js +74 -0
- package/src/services/ResultService.js +252 -0
- package/src/services/RunMatrixService.js +309 -0
- package/src/utils/PayloadBuilder.js +280 -0
- package/src/utils/RunDataNormalizer.js +335 -0
- package/src/utils/UuidValidator.js +102 -0
- package/src/utils/errors.js +217 -0
- package/src/utils/index.js +17 -0
- package/src/utils/logger.js +124 -0
- package/src/utils/mapAppqUuid.js +83 -0
- package/src/utils/validator.js +157 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Setup for JWT Browser Authentication
|
|
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
|
|
21
|
+
*
|
|
22
|
+
* @module playwright/global-setup
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { chromium } = require('@playwright/test');
|
|
26
|
+
const axios = require('axios');
|
|
27
|
+
const https = require('https');
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Setup JWT-based browser authentication
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} config - Playwright config object
|
|
35
|
+
* @returns {Promise<void>}
|
|
36
|
+
*/
|
|
37
|
+
async function globalSetup(config) {
|
|
38
|
+
console.log('\nš Setting up JWT browser authentication...\n');
|
|
39
|
+
|
|
40
|
+
// Extract SDK configuration from reporter config or use env
|
|
41
|
+
const sdkConfig = extractSDKConfig(config);
|
|
42
|
+
|
|
43
|
+
// Validate required configuration
|
|
44
|
+
if (!sdkConfig.apiKey) {
|
|
45
|
+
console.error('ā APPLIQATION_API_KEY is required for JWT authentication');
|
|
46
|
+
console.error(' Please add to your .env file:\n');
|
|
47
|
+
console.error(' APPLIQATION_API_KEY=appq_live_xxxxx\n');
|
|
48
|
+
throw new Error('Missing APPLIQATION_API_KEY');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!sdkConfig.baseUrl) {
|
|
52
|
+
console.error('ā APPLIQATION_BASE_URL is required');
|
|
53
|
+
throw new Error('Missing APPLIQATION_BASE_URL');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Determine if JWT auth is enabled
|
|
57
|
+
const useJwtAuth = sdkConfig.useJwtAuth !== false; // Default: true
|
|
58
|
+
|
|
59
|
+
if (!useJwtAuth) {
|
|
60
|
+
console.log('ā¹ļø JWT authentication disabled. Using legacy session auth.\n');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Step 1: Get JWT token from API
|
|
66
|
+
console.log('š” Requesting JWT token from Appliqation backend...');
|
|
67
|
+
const jwtData = await getJWTToken(sdkConfig);
|
|
68
|
+
|
|
69
|
+
if (!jwtData.jwt_token) {
|
|
70
|
+
console.warn('ā ļø No JWT token received. Falling back to legacy auth.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`ā
JWT token received (expires in 1 hour)`);
|
|
75
|
+
console.log(` Run ID: ${jwtData.run_id}\n`);
|
|
76
|
+
|
|
77
|
+
// Step 2: Setup browser with JWT
|
|
78
|
+
console.log('š Setting up browser session with JWT...');
|
|
79
|
+
await setupBrowserWithJWT(sdkConfig, jwtData.jwt_token);
|
|
80
|
+
|
|
81
|
+
console.log('ā
Browser authentication setup complete!');
|
|
82
|
+
console.log('š Authenticated state saved to: .auth/jwt.json\n');
|
|
83
|
+
console.log('⨠Tests will run with JWT authentication!\n');
|
|
84
|
+
|
|
85
|
+
// Store for use in tests
|
|
86
|
+
process.env.APPLIQATION_JWT_TOKEN = jwtData.jwt_token;
|
|
87
|
+
process.env.APPLIQATION_RUN_ID = jwtData.run_id;
|
|
88
|
+
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('\nā JWT authentication setup failed:', error.message);
|
|
91
|
+
console.error('\nš” Troubleshooting:');
|
|
92
|
+
console.error(' 1. Check APPLIQATION_API_KEY is valid');
|
|
93
|
+
console.error(' 2. Check APPLIQATION_BASE_URL is accessible');
|
|
94
|
+
console.error(' 3. Check backend has QONSOLE_JWT_SECRET configured');
|
|
95
|
+
console.error(' 4. Try running: npm run setup-auth (legacy method)\n');
|
|
96
|
+
|
|
97
|
+
// Don't fail the test run - fall back to legacy auth
|
|
98
|
+
console.warn('ā ļø Falling back to legacy authentication method\n');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract SDK configuration from Playwright config
|
|
104
|
+
*
|
|
105
|
+
* @param {Object} config - Playwright config
|
|
106
|
+
* @returns {Object} SDK configuration
|
|
107
|
+
*/
|
|
108
|
+
function extractSDKConfig(config) {
|
|
109
|
+
// Try to find SDK config in reporter configuration
|
|
110
|
+
let sdkConfig = {};
|
|
111
|
+
|
|
112
|
+
if (config.reporter) {
|
|
113
|
+
const reporters = Array.isArray(config.reporter) ? config.reporter : [config.reporter];
|
|
114
|
+
|
|
115
|
+
for (const reporter of reporters) {
|
|
116
|
+
if (Array.isArray(reporter)) {
|
|
117
|
+
const [reporterPath, reporterConfig] = reporter;
|
|
118
|
+
if (reporterPath && reporterPath.includes('appliqation')) {
|
|
119
|
+
sdkConfig = reporterConfig || {};
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fallback to environment variables
|
|
127
|
+
return {
|
|
128
|
+
baseUrl: sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL,
|
|
129
|
+
apiKey: sdkConfig.apiKey || process.env.APPLIQATION_API_KEY,
|
|
130
|
+
projectKey: sdkConfig.projectKey || process.env.APPLIQATION_PROJECT_KEY,
|
|
131
|
+
scenarioId: sdkConfig.scenarioId || parseInt(process.env.APPLIQATION_SCENARIO_ID) || 0,
|
|
132
|
+
environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT || 'Local',
|
|
133
|
+
appUrl: sdkConfig.appUrl || process.env.APPLIQATION_APP_URL || sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL,
|
|
134
|
+
useJwtAuth: sdkConfig.useJwtAuth !== undefined ? sdkConfig.useJwtAuth : process.env.APPLIQATION_USE_JWT_AUTH !== 'false',
|
|
135
|
+
runTitle: sdkConfig.runTitle || process.env.APPLIQATION_RUN_TITLE || `JWT Auth Setup - ${new Date().toISOString()}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get JWT token from Appliqation API
|
|
141
|
+
*
|
|
142
|
+
* @param {Object} sdkConfig - SDK configuration
|
|
143
|
+
* @returns {Promise<Object>} JWT data { jwt_token, run_id }
|
|
144
|
+
*/
|
|
145
|
+
async function getJWTToken(sdkConfig) {
|
|
146
|
+
const endpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/automation/run/create`;
|
|
147
|
+
|
|
148
|
+
const payload = {
|
|
149
|
+
project_key: sdkConfig.projectKey,
|
|
150
|
+
scenario_id: sdkConfig.scenarioId || 0,
|
|
151
|
+
environment: sdkConfig.environment,
|
|
152
|
+
title: sdkConfig.runTitle,
|
|
153
|
+
browsers: ['Chrome'], // Default browser for setup
|
|
154
|
+
device: 'Desktop',
|
|
155
|
+
os: process.platform === 'darwin' ? 'macOS' : process.platform === 'win32' ? 'Windows' : 'Linux',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const response = await axios.post(endpoint, payload, {
|
|
160
|
+
headers: {
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
'X-API-Key': sdkConfig.apiKey,
|
|
163
|
+
},
|
|
164
|
+
httpsAgent: new https.Agent({
|
|
165
|
+
rejectUnauthorized: false, // Allow self-signed certs in dev
|
|
166
|
+
}),
|
|
167
|
+
validateStatus: (status) => status < 500,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
171
|
+
throw new Error(`API returned status ${response.status}: ${response.data?.message || 'Unknown error'}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
jwt_token: response.data.jwt_token,
|
|
176
|
+
run_id: response.data.run_id,
|
|
177
|
+
metadata: response.data.metadata,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error.response) {
|
|
182
|
+
throw new Error(`API error: ${error.response.status} - ${error.response.data?.message || error.message}`);
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Network error: ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Setup browser session with JWT token
|
|
190
|
+
*
|
|
191
|
+
* @param {Object} sdkConfig - SDK configuration
|
|
192
|
+
* @param {string} jwtToken - JWT token
|
|
193
|
+
* @returns {Promise<void>}
|
|
194
|
+
*/
|
|
195
|
+
async function setupBrowserWithJWT(sdkConfig, jwtToken) {
|
|
196
|
+
const browser = await chromium.launch({
|
|
197
|
+
headless: true,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const context = await browser.newContext({
|
|
201
|
+
ignoreHTTPSErrors: true, // Allow self-signed certs in dev
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const page = await context.newPage();
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Navigate to app with JWT token in query parameter
|
|
208
|
+
const appUrl = sdkConfig.appUrl.replace(/\/$/, '');
|
|
209
|
+
const separator = appUrl.includes('?') ? '&' : '?';
|
|
210
|
+
const urlWithToken = `${appUrl}${separator}qonsole_token=${encodeURIComponent(jwtToken)}`;
|
|
211
|
+
|
|
212
|
+
console.log(` Navigating to: ${appUrl}...`);
|
|
213
|
+
await page.goto(urlWithToken, {
|
|
214
|
+
waitUntil: 'networkidle',
|
|
215
|
+
timeout: 30000,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Wait a bit for session to be established
|
|
219
|
+
await page.waitForTimeout(2000);
|
|
220
|
+
|
|
221
|
+
// Verify authentication worked
|
|
222
|
+
const currentUrl = page.url();
|
|
223
|
+
if (currentUrl.includes('/user/login')) {
|
|
224
|
+
throw new Error('JWT authentication failed - redirected to login page');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Save authenticated state
|
|
228
|
+
const authDir = path.resolve('.auth');
|
|
229
|
+
if (!fs.existsSync(authDir)) {
|
|
230
|
+
fs.mkdirSync(authDir, { recursive: true });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await context.storageState({ path: '.auth/jwt.json' });
|
|
234
|
+
console.log(' ā Authenticated state saved');
|
|
235
|
+
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw new Error(`Browser setup failed: ${error.message}`);
|
|
238
|
+
} finally {
|
|
239
|
+
await browser.close();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = globalSetup;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Browser Authentication Helper
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for managing JWT-based browser authentication in Playwright tests.
|
|
5
|
+
* Handles token injection, validation, and refresh for both Appliqation and external apps.
|
|
6
|
+
*
|
|
7
|
+
* @module playwright/helpers/jwt-browser-auth
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const https = require('https');
|
|
12
|
+
|
|
13
|
+
class JWTBrowserAuth {
|
|
14
|
+
/**
|
|
15
|
+
* Create a JWT Browser Auth helper
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} config - Configuration object
|
|
18
|
+
* @param {string} config.baseUrl - Appliqation backend URL
|
|
19
|
+
* @param {string} config.apiKey - API key for authentication
|
|
20
|
+
* @param {string} config.appUrl - Application URL to test (optional, defaults to baseUrl)
|
|
21
|
+
*/
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.baseUrl = config.baseUrl;
|
|
24
|
+
this.apiKey = config.apiKey;
|
|
25
|
+
this.appUrl = config.appUrl || config.baseUrl;
|
|
26
|
+
this.jwtToken = null;
|
|
27
|
+
this.jwtExpiry = null;
|
|
28
|
+
this.runId = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get current JWT token, refreshing if expired
|
|
33
|
+
*
|
|
34
|
+
* @returns {Promise<string>} JWT token
|
|
35
|
+
*/
|
|
36
|
+
async getToken() {
|
|
37
|
+
if (this.jwtToken && !this.isTokenExpired()) {
|
|
38
|
+
return this.jwtToken;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Token expired or doesn't exist, get new one
|
|
42
|
+
await this.refreshToken();
|
|
43
|
+
return this.jwtToken;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if current token is expired
|
|
48
|
+
*
|
|
49
|
+
* @returns {boolean} True if expired
|
|
50
|
+
*/
|
|
51
|
+
isTokenExpired() {
|
|
52
|
+
if (!this.jwtExpiry) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Consider expired if less than 5 minutes remaining
|
|
57
|
+
const now = Math.floor(Date.now() / 1000);
|
|
58
|
+
return (this.jwtExpiry - now) < 300;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Refresh JWT token from API
|
|
63
|
+
*
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
async refreshToken() {
|
|
67
|
+
const endpoint = `${this.baseUrl.replace(/\/$/, '')}/api/automation/run/create`;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await axios.post(
|
|
71
|
+
endpoint,
|
|
72
|
+
{
|
|
73
|
+
project_key: process.env.APPLIQATION_PROJECT_KEY,
|
|
74
|
+
scenario_id: parseInt(process.env.APPLIQATION_SCENARIO_ID) || 0,
|
|
75
|
+
environment: process.env.APPLIQATION_ENVIRONMENT || 'Local',
|
|
76
|
+
title: `JWT Refresh - ${new Date().toISOString()}`,
|
|
77
|
+
browsers: ['Chrome'],
|
|
78
|
+
device: 'Desktop',
|
|
79
|
+
os: process.platform === 'darwin' ? 'macOS' : process.platform === 'win32' ? 'Windows' : 'Linux',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'X-API-Key': this.apiKey,
|
|
85
|
+
},
|
|
86
|
+
httpsAgent: new https.Agent({
|
|
87
|
+
rejectUnauthorized: false,
|
|
88
|
+
}),
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
this.jwtToken = response.data.jwt_token;
|
|
93
|
+
this.runId = response.data.run_id;
|
|
94
|
+
this.jwtExpiry = this.parseJwtExpiry(this.jwtToken);
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Failed to refresh JWT token: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parse JWT token to extract expiry time
|
|
103
|
+
*
|
|
104
|
+
* @param {string} token - JWT token
|
|
105
|
+
* @returns {number|null} Unix timestamp of expiry
|
|
106
|
+
*/
|
|
107
|
+
parseJwtExpiry(token) {
|
|
108
|
+
try {
|
|
109
|
+
const parts = token.split('.');
|
|
110
|
+
if (parts.length !== 3) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
115
|
+
return payload.exp || null;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn('Failed to parse JWT expiry:', error.message);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Inject JWT into browser context via URL navigation
|
|
124
|
+
*
|
|
125
|
+
* @param {import('@playwright/test').Page} page - Playwright page
|
|
126
|
+
* @param {string} targetUrl - URL to navigate to (optional)
|
|
127
|
+
* @returns {Promise<void>}
|
|
128
|
+
*/
|
|
129
|
+
async injectJwtIntoContext(page, targetUrl = null) {
|
|
130
|
+
const token = await this.getToken();
|
|
131
|
+
const url = targetUrl || this.appUrl;
|
|
132
|
+
|
|
133
|
+
// Add JWT as query parameter
|
|
134
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
135
|
+
const urlWithToken = `${url}${separator}qonsole_token=${encodeURIComponent(token)}`;
|
|
136
|
+
|
|
137
|
+
await page.goto(urlWithToken, {
|
|
138
|
+
waitUntil: 'networkidle',
|
|
139
|
+
timeout: 30000,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate that JWT session is active in browser
|
|
145
|
+
*
|
|
146
|
+
* @param {import('@playwright/test').Page} page - Playwright page
|
|
147
|
+
* @returns {Promise<boolean>} True if session is active
|
|
148
|
+
*/
|
|
149
|
+
async validateJwtSession(page) {
|
|
150
|
+
try {
|
|
151
|
+
// Check if we're on login page (authentication failed)
|
|
152
|
+
const currentUrl = page.url();
|
|
153
|
+
if (currentUrl.includes('/user/login')) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for common authenticated elements
|
|
158
|
+
const isAuthenticated = await page.evaluate(() => {
|
|
159
|
+
// Check for user menu or authenticated indicators
|
|
160
|
+
const userMenu = document.querySelector('.user-menu, [data-user-menu], #user-menu');
|
|
161
|
+
const logoutLink = document.querySelector('a[href*="logout"]');
|
|
162
|
+
const loginForm = document.querySelector('form[action*="login"]');
|
|
163
|
+
|
|
164
|
+
return (userMenu !== null || logoutLink !== null) && loginForm === null;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return isAuthenticated;
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.warn('Failed to validate JWT session:', error.message);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get authenticated URL with JWT token
|
|
177
|
+
*
|
|
178
|
+
* @param {string} path - Path to append to base URL (optional)
|
|
179
|
+
* @returns {Promise<string>} URL with JWT token
|
|
180
|
+
*/
|
|
181
|
+
async getAuthenticatedUrl(path = '') {
|
|
182
|
+
const token = await this.getToken();
|
|
183
|
+
const baseUrl = this.appUrl.replace(/\/$/, '');
|
|
184
|
+
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
185
|
+
const url = `${baseUrl}${fullPath}`;
|
|
186
|
+
|
|
187
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
188
|
+
return `${url}${separator}qonsole_token=${encodeURIComponent(token)}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Refresh token if needed before long-running operations
|
|
193
|
+
*
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
async refreshJwtIfNeeded() {
|
|
197
|
+
if (this.isTokenExpired()) {
|
|
198
|
+
console.log('š JWT token expired, refreshing...');
|
|
199
|
+
await this.refreshToken();
|
|
200
|
+
console.log('ā
JWT token refreshed');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get current run ID
|
|
206
|
+
*
|
|
207
|
+
* @returns {string|null} Run ID
|
|
208
|
+
*/
|
|
209
|
+
getRunId() {
|
|
210
|
+
return this.runId;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create JWT auth helper from environment variables
|
|
215
|
+
*
|
|
216
|
+
* @returns {JWTBrowserAuth} Auth helper instance
|
|
217
|
+
*/
|
|
218
|
+
static fromEnv() {
|
|
219
|
+
return new JWTBrowserAuth({
|
|
220
|
+
baseUrl: process.env.APPLIQATION_BASE_URL,
|
|
221
|
+
apiKey: process.env.APPLIQATION_API_KEY,
|
|
222
|
+
appUrl: process.env.APPLIQATION_APP_URL || process.env.APPLIQATION_BASE_URL,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = { JWTBrowserAuth };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appliqation Playwright Integration
|
|
3
|
+
*
|
|
4
|
+
* Provides Playwright-specific utilities including:
|
|
5
|
+
* - JWT browser authentication with auto-refresh
|
|
6
|
+
* - Playwright test fixture with automatic token management
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const JwtBrowserAuth = require('./JwtBrowserAuth');
|
|
10
|
+
const { test, expect } = require('./fixture');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
JwtBrowserAuth,
|
|
14
|
+
test,
|
|
15
|
+
expect
|
|
16
|
+
};
|