@qate/cli 1.0.0 → 1.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/README.md +449 -449
- package/dist/AuthFlowExecutor.d.ts +87 -0
- package/dist/AuthFlowExecutor.d.ts.map +1 -0
- package/dist/AuthFlowExecutor.js +351 -0
- package/dist/AuthFlowExecutor.js.map +1 -0
- package/dist/AxiosGenerator.js +28 -28
- package/dist/JunitXmlGenerator.d.ts +12 -0
- package/dist/JunitXmlGenerator.d.ts.map +1 -0
- package/dist/JunitXmlGenerator.js +114 -0
- package/dist/JunitXmlGenerator.js.map +1 -0
- package/dist/PlaywrightGenerator.d.ts +1 -1
- package/dist/PlaywrightGenerator.d.ts.map +1 -1
- package/dist/PlaywrightGenerator.js +796 -758
- package/dist/PlaywrightGenerator.js.map +1 -1
- package/dist/RestApiExecutor.d.ts +3 -4
- package/dist/RestApiExecutor.d.ts.map +1 -1
- package/dist/RestApiExecutor.js +63 -3
- package/dist/RestApiExecutor.js.map +1 -1
- package/dist/cli.js +536 -148
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +78 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -6
- package/dist/client.js.map +1 -1
- package/package.json +71 -71
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ const RestApiExecutor_1 = require("./RestApiExecutor");
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const os = __importStar(require("os"));
|
|
49
|
+
const JunitXmlGenerator_1 = require("./JunitXmlGenerator");
|
|
49
50
|
const VERSION = '1.0.0';
|
|
50
51
|
const program = new commander_1.Command();
|
|
51
52
|
// Helper to handle API errors with detailed feedback
|
|
@@ -126,6 +127,39 @@ function getConfig(options) {
|
|
|
126
127
|
}
|
|
127
128
|
return { apiKey, baseUrl };
|
|
128
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Detect PR number from CI environment variables.
|
|
132
|
+
* Supports GitHub Actions, GitLab CI, Bitbucket Pipelines, Azure DevOps, Jenkins, CircleCI.
|
|
133
|
+
*/
|
|
134
|
+
function detectPRFromEnvironment() {
|
|
135
|
+
// GitHub Actions: GITHUB_REF = refs/pull/123/merge
|
|
136
|
+
const githubRef = process.env.GITHUB_REF;
|
|
137
|
+
if (githubRef?.includes('/pull/')) {
|
|
138
|
+
const match = githubRef.match(/\/pull\/(\d+)\//);
|
|
139
|
+
if (match)
|
|
140
|
+
return parseInt(match[1]);
|
|
141
|
+
}
|
|
142
|
+
// GitLab CI
|
|
143
|
+
if (process.env.CI_MERGE_REQUEST_IID)
|
|
144
|
+
return parseInt(process.env.CI_MERGE_REQUEST_IID);
|
|
145
|
+
// Bitbucket Pipelines
|
|
146
|
+
if (process.env.BITBUCKET_PR_ID)
|
|
147
|
+
return parseInt(process.env.BITBUCKET_PR_ID);
|
|
148
|
+
// Azure DevOps
|
|
149
|
+
if (process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER)
|
|
150
|
+
return parseInt(process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER);
|
|
151
|
+
// Jenkins
|
|
152
|
+
if (process.env.CHANGE_ID)
|
|
153
|
+
return parseInt(process.env.CHANGE_ID);
|
|
154
|
+
// CircleCI: CIRCLE_PULL_REQUEST = https://github.com/org/repo/pull/123
|
|
155
|
+
const circlePR = process.env.CIRCLE_PULL_REQUEST;
|
|
156
|
+
if (circlePR) {
|
|
157
|
+
const match = circlePR.match(/\/(\d+)$/);
|
|
158
|
+
if (match)
|
|
159
|
+
return parseInt(match[1]);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
129
163
|
// Helper to display execution status
|
|
130
164
|
function displayStatus(status, type) {
|
|
131
165
|
const name = type === 'testset' ? status.testSetName : status.sequenceName;
|
|
@@ -172,171 +206,422 @@ program
|
|
|
172
206
|
.name('qate')
|
|
173
207
|
.description('Qate CLI for CI/CD pipeline integration\n\nGenerate and run Playwright tests from your Qate test definitions.')
|
|
174
208
|
.version(VERSION)
|
|
175
|
-
.addHelpText('after', `
|
|
176
|
-
Examples:
|
|
177
|
-
$ qate list List all test sets
|
|
178
|
-
$ qate generate -n "My Tests" -o ./e2e Generate Playwright tests (web apps)
|
|
179
|
-
$ qate run -n "API Tests" --wait Run REST/SOAP tests locally
|
|
180
|
-
$ qate export:testset -n "Tests" -o ./out Export as Playwright or Axios tests
|
|
181
|
-
$ qate status -e ci_xxx_xxx Check execution status
|
|
182
|
-
|
|
183
|
-
Documentation:
|
|
184
|
-
https://docs.qate.io/cli
|
|
185
|
-
|
|
186
|
-
Environment Variables:
|
|
187
|
-
QATE_API_KEY Your Qate API key (required)
|
|
188
|
-
QATE_API_URL Qate API URL (default: https://api.qate.ai)
|
|
209
|
+
.addHelpText('after', `
|
|
210
|
+
Examples:
|
|
211
|
+
$ qate list List all test sets
|
|
212
|
+
$ qate generate -n "My Tests" -o ./e2e Generate Playwright tests (web apps)
|
|
213
|
+
$ qate run -n "API Tests" --wait Run REST/SOAP tests locally
|
|
214
|
+
$ qate export:testset -n "Tests" -o ./out Export as Playwright or Axios tests
|
|
215
|
+
$ qate status -e ci_xxx_xxx Check execution status
|
|
216
|
+
|
|
217
|
+
Documentation:
|
|
218
|
+
https://docs.qate.io/cli
|
|
219
|
+
|
|
220
|
+
Environment Variables:
|
|
221
|
+
QATE_API_KEY Your Qate API key (required)
|
|
222
|
+
QATE_API_URL Qate API URL (default: https://api.qate.ai)
|
|
189
223
|
`);
|
|
190
224
|
// ================== TEST SET COMMANDS ==================
|
|
191
225
|
program
|
|
192
226
|
.command('run:testset')
|
|
193
227
|
.alias('run')
|
|
194
228
|
.description('Execute a test set locally (REST API and SOAP only - use "generate" for web apps)')
|
|
195
|
-
.
|
|
229
|
+
.option('-n, --name <name>', 'Name of the test set to execute')
|
|
196
230
|
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
197
231
|
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
198
|
-
.option('--app <applicationId>', 'Application ID (
|
|
232
|
+
.option('--app <applicationId>', 'Application ID (required for --smart, optional otherwise)')
|
|
233
|
+
.option('--smart', 'Use AI to select tests based on PR changes')
|
|
234
|
+
.option('--pr <number>', 'PR number (auto-detected from CI environment if not specified)')
|
|
235
|
+
.option('--repo-type <type>', 'Repository type for --smart: frontend or backend', 'frontend')
|
|
199
236
|
.option('-w, --wait', 'Wait for execution to complete')
|
|
200
237
|
.option('--timeout <seconds>', 'Timeout for waiting (default: 600)', '600')
|
|
201
238
|
.option('--json', 'Output results as JSON')
|
|
239
|
+
.option('--junit <path>', 'Write JUnit XML report to file')
|
|
202
240
|
.option('-v, --verbose', 'Show detailed step-by-step output')
|
|
203
241
|
.action(async (options) => {
|
|
204
242
|
try {
|
|
243
|
+
// Validate: either --name or --smart is required
|
|
244
|
+
if (!options.smart && !options.name) {
|
|
245
|
+
console.error(chalk_1.default.red('Error: Either --name <test-set-name> or --smart is required.'));
|
|
246
|
+
console.error(chalk_1.default.gray(' Use --name to run a specific test set'));
|
|
247
|
+
console.error(chalk_1.default.gray(' Use --smart to let AI select tests based on PR changes'));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
if (options.smart && !options.app) {
|
|
251
|
+
console.error(chalk_1.default.red('Error: --app <applicationId> is required when using --smart.'));
|
|
252
|
+
console.error(chalk_1.default.gray(' Find your application ID in Qate: Settings > Applications'));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
205
255
|
const { apiKey, baseUrl } = getConfig(options);
|
|
206
256
|
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
257
|
+
if (options.smart) {
|
|
258
|
+
// Smart run: AI-powered test selection based on PR changes
|
|
259
|
+
const prNumber = options.pr ? parseInt(options.pr) : detectPRFromEnvironment();
|
|
260
|
+
if (!prNumber) {
|
|
261
|
+
console.error(chalk_1.default.red('Error: Could not detect PR number from CI environment.'));
|
|
262
|
+
console.error(chalk_1.default.gray(' Use --pr <number> to specify the PR number explicitly.'));
|
|
263
|
+
console.error(chalk_1.default.gray(' Auto-detection supports: GitHub Actions, GitLab CI, Bitbucket Pipelines,'));
|
|
264
|
+
console.error(chalk_1.default.gray(' Azure DevOps, Jenkins, CircleCI'));
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
215
267
|
if (!options.json) {
|
|
216
|
-
console.log(chalk_1.default.
|
|
217
|
-
console.log(chalk_1.default.gray(`Application: ${
|
|
218
|
-
console.log(chalk_1.default.gray(`
|
|
219
|
-
console.log(chalk_1.default.gray(
|
|
220
|
-
console.log();
|
|
268
|
+
console.log(chalk_1.default.blue(`Smart run: analyzing PR #${prNumber}...`));
|
|
269
|
+
console.log(chalk_1.default.gray(` Application: ${options.app}`));
|
|
270
|
+
console.log(chalk_1.default.gray(` Repository type: ${options.repoType}`));
|
|
271
|
+
console.log(chalk_1.default.gray(' Analyzing PR changes with AI...'));
|
|
221
272
|
}
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
273
|
+
// Call smart-generate to get AI-selected tests (also checks execution limits + token balance)
|
|
274
|
+
const exportData = await client.smartGenerate(options.app, prNumber, options.repoType);
|
|
275
|
+
const executionId = exportData.executionId;
|
|
276
|
+
if (exportData.tests.length === 0) {
|
|
277
|
+
if (options.json) {
|
|
278
|
+
console.log(JSON.stringify({
|
|
279
|
+
status: 'skipped',
|
|
280
|
+
message: 'AI analysis found no tests to execute for this PR',
|
|
281
|
+
}, null, 2));
|
|
229
282
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!options.json) {
|
|
233
|
-
console.log(chalk_1.default.yellow('Note: Could not create execution record'));
|
|
283
|
+
else {
|
|
284
|
+
console.log(chalk_1.default.yellow('\nAI analysis found no tests to execute for this PR.'));
|
|
234
285
|
}
|
|
286
|
+
process.exit(0);
|
|
235
287
|
}
|
|
236
|
-
// Execute tests locally
|
|
237
288
|
if (!options.json) {
|
|
238
|
-
console.log(chalk_1.default.
|
|
289
|
+
console.log(chalk_1.default.green(`\nAI selected ${exportData.tests.length} test(s) to execute`));
|
|
239
290
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
291
|
+
const appType = exportData.application?.type || 'web';
|
|
292
|
+
if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
293
|
+
// REST API / SOAP: execute locally
|
|
243
294
|
if (!options.json) {
|
|
244
|
-
console.log(
|
|
295
|
+
console.log(chalk_1.default.cyan(`\nREST API application detected - executing locally`));
|
|
296
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
297
|
+
console.log(chalk_1.default.gray(`Base URL: ${exportData.application.url}`));
|
|
298
|
+
console.log(chalk_1.default.gray(`Tests: ${exportData.tests.length}`));
|
|
299
|
+
if (executionId) {
|
|
300
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
301
|
+
}
|
|
302
|
+
console.log();
|
|
245
303
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
} : undefined);
|
|
251
|
-
// Calculate summary
|
|
252
|
-
const summary = (0, RestApiExecutor_1.calculateSummary)(results);
|
|
253
|
-
// Report results to backend
|
|
254
|
-
if (executionId) {
|
|
255
|
-
try {
|
|
256
|
-
await client.reportResults(executionId, results, summary, 'cli', {
|
|
257
|
-
os: os.platform(),
|
|
258
|
-
nodeVersion: process.version
|
|
259
|
-
});
|
|
304
|
+
if (!options.json) {
|
|
305
|
+
console.log(chalk_1.default.blue(`Executing ${exportData.tests.length} tests...\n`));
|
|
306
|
+
}
|
|
307
|
+
const results = await (0, RestApiExecutor_1.executeTests)(exportData.tests, exportData.application.url, { verbose: options.verbose, client, applicationId: exportData.application.id }, (testResult, index, total) => {
|
|
260
308
|
if (!options.json) {
|
|
261
|
-
console.log(
|
|
309
|
+
console.log((0, RestApiExecutor_1.formatTestResult)(testResult, options.verbose));
|
|
310
|
+
}
|
|
311
|
+
}, options.verbose ? (progress, stepResult) => { } : undefined);
|
|
312
|
+
const summary = (0, RestApiExecutor_1.calculateSummary)(results);
|
|
313
|
+
// Report results to backend (pass applicationId for smart execution)
|
|
314
|
+
if (executionId) {
|
|
315
|
+
try {
|
|
316
|
+
await client.reportResults(executionId, results, summary, 'cli', { os: os.platform(), nodeVersion: process.version }, exportData.application.id);
|
|
317
|
+
if (!options.json) {
|
|
318
|
+
console.log(chalk_1.default.gray(`\nResults reported to Qate`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
if (!options.json) {
|
|
323
|
+
console.log(chalk_1.default.yellow('Note: Could not report results to backend'));
|
|
324
|
+
}
|
|
262
325
|
}
|
|
263
326
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
327
|
+
// Write JUnit XML report if requested
|
|
328
|
+
if (options.junit) {
|
|
329
|
+
try {
|
|
330
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXml)(results, 'Smart PR Analysis');
|
|
331
|
+
const junitPath = path.resolve(options.junit);
|
|
332
|
+
const junitDir = path.dirname(junitPath);
|
|
333
|
+
if (!fs.existsSync(junitDir)) {
|
|
334
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
335
|
+
}
|
|
336
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
337
|
+
if (!options.json) {
|
|
338
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
if (!options.json) {
|
|
343
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
344
|
+
}
|
|
267
345
|
}
|
|
268
346
|
}
|
|
347
|
+
// Display final summary
|
|
348
|
+
if (options.json) {
|
|
349
|
+
console.log(JSON.stringify({
|
|
350
|
+
executionId,
|
|
351
|
+
mode: 'smart',
|
|
352
|
+
prNumber,
|
|
353
|
+
status: summary.status,
|
|
354
|
+
summary: {
|
|
355
|
+
total: summary.total,
|
|
356
|
+
passed: summary.passed,
|
|
357
|
+
failed: summary.failed,
|
|
358
|
+
error: summary.error
|
|
359
|
+
},
|
|
360
|
+
tests: results.map(r => ({
|
|
361
|
+
testId: r.testId,
|
|
362
|
+
testName: r.testName,
|
|
363
|
+
status: r.status,
|
|
364
|
+
duration: r.duration
|
|
365
|
+
}))
|
|
366
|
+
}, null, 2));
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
console.log();
|
|
370
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
371
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
372
|
+
console.log(` Total: ${summary.total}`);
|
|
373
|
+
console.log(` Passed: ${chalk_1.default.green(summary.passed.toString())}`);
|
|
374
|
+
console.log(` Failed: ${chalk_1.default.red(summary.failed.toString())}`);
|
|
375
|
+
console.log(` Error: ${chalk_1.default.red(summary.error.toString())}`);
|
|
376
|
+
console.log();
|
|
377
|
+
const statusColor = summary.status === 'passed' ? chalk_1.default.green : chalk_1.default.red;
|
|
378
|
+
console.log(`Status: ${statusColor(summary.status.toUpperCase())}`);
|
|
379
|
+
}
|
|
380
|
+
process.exit(summary.status === 'passed' ? 0 : 1);
|
|
269
381
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
382
|
+
else if (appType === 'desktop') {
|
|
383
|
+
// Desktop: trigger remote execution via smart-execute endpoint
|
|
384
|
+
if (!options.json) {
|
|
385
|
+
console.log(chalk_1.default.cyan('\nDesktop application detected - executing via connected agent'));
|
|
386
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
387
|
+
}
|
|
388
|
+
const testIds = exportData.tests.map(t => t.id);
|
|
389
|
+
const result = await client.smartExecute(options.app, testIds, prNumber);
|
|
390
|
+
if (!options.json) {
|
|
391
|
+
console.log(chalk_1.default.gray(`Execution ID: ${result.executionId}`));
|
|
392
|
+
console.log(chalk_1.default.blue('\nWaiting for execution to complete...\n'));
|
|
393
|
+
}
|
|
394
|
+
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
395
|
+
// MUST pass testCount as expectedCount or polling hangs forever
|
|
396
|
+
const status = await client.pollExecution(result.executionId, 'testset', timeoutMs, 5000, result.testCount);
|
|
397
|
+
// Write JUnit XML report if requested
|
|
398
|
+
if (options.junit) {
|
|
399
|
+
try {
|
|
400
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXmlFromStatus)(status, 'Smart PR Analysis');
|
|
401
|
+
const junitPath = path.resolve(options.junit);
|
|
402
|
+
const junitDir = path.dirname(junitPath);
|
|
403
|
+
if (!fs.existsSync(junitDir)) {
|
|
404
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
405
|
+
}
|
|
406
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
407
|
+
if (!options.json) {
|
|
408
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
if (!options.json) {
|
|
413
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (options.json) {
|
|
418
|
+
console.log(JSON.stringify({ ...status, mode: 'smart', prNumber }, null, 2));
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
displayStatus(status, 'testset');
|
|
422
|
+
}
|
|
423
|
+
process.exit(status.status === 'passed' ? 0 : 1);
|
|
289
424
|
}
|
|
290
425
|
else {
|
|
426
|
+
// Web apps should use 'qate generate --smart'
|
|
427
|
+
console.error(chalk_1.default.red('\nError: "qate run --smart" is not supported for web applications.'));
|
|
291
428
|
console.log();
|
|
292
|
-
console.log(chalk_1.default.
|
|
293
|
-
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
294
|
-
console.log(` Total: ${summary.total}`);
|
|
295
|
-
console.log(` Passed: ${chalk_1.default.green(summary.passed.toString())}`);
|
|
296
|
-
console.log(` Failed: ${chalk_1.default.red(summary.failed.toString())}`);
|
|
297
|
-
console.log(` Error: ${chalk_1.default.red(summary.error.toString())}`);
|
|
429
|
+
console.log(chalk_1.default.yellow('For web applications, use "qate generate --smart" to create Playwright tests:'));
|
|
298
430
|
console.log();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
console.log(
|
|
431
|
+
console.log(chalk_1.default.white(` qate generate --smart --app ${options.app} -o ./e2e`));
|
|
432
|
+
console.log(chalk_1.default.white(` cd ./e2e && npm install`));
|
|
433
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
434
|
+
console.log();
|
|
435
|
+
console.log(chalk_1.default.gray('This generates Playwright test files that you can run locally or in CI/CD.'));
|
|
436
|
+
process.exit(1);
|
|
302
437
|
}
|
|
303
|
-
process.exit(summary.status === 'passed' ? 0 : 1);
|
|
304
438
|
}
|
|
305
|
-
else
|
|
306
|
-
//
|
|
439
|
+
else {
|
|
440
|
+
// Normal flow (--name provided)
|
|
307
441
|
if (!options.json) {
|
|
308
|
-
console.log(chalk_1.default.
|
|
309
|
-
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
442
|
+
console.log(chalk_1.default.blue(`Fetching test set: ${options.name}`));
|
|
310
443
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
444
|
+
// First, export the test set to check application type
|
|
445
|
+
const exportData = await client.exportTestSet(options.name, options.app);
|
|
446
|
+
const appType = exportData.application?.type || 'web';
|
|
447
|
+
// REST API apps execute locally in the CLI
|
|
448
|
+
if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
449
|
+
if (!options.json) {
|
|
450
|
+
console.log(chalk_1.default.cyan(`REST API application detected - executing locally`));
|
|
451
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
452
|
+
console.log(chalk_1.default.gray(`Base URL: ${exportData.application.url}`));
|
|
453
|
+
console.log(chalk_1.default.gray(`Tests: ${exportData.tests.length}`));
|
|
454
|
+
console.log();
|
|
455
|
+
}
|
|
456
|
+
// Create execution record for tracking
|
|
457
|
+
let executionId;
|
|
458
|
+
try {
|
|
459
|
+
const generateResult = await client.createGenerateExecution(options.name, undefined, options.app, 'cli');
|
|
460
|
+
executionId = generateResult.executionId;
|
|
461
|
+
if (!options.json) {
|
|
462
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
if (err.response?.status === 429 || err.response?.status === 402) {
|
|
467
|
+
const msg = err.response?.data?.message || 'Execution not allowed';
|
|
468
|
+
if (options.json) {
|
|
469
|
+
console.log(JSON.stringify({ error: err.response?.data?.error || 'limit_reached', message: msg }));
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
console.error(chalk_1.default.red(msg));
|
|
473
|
+
}
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
if (!options.json) {
|
|
477
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record'));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Execute tests locally
|
|
481
|
+
if (!options.json) {
|
|
482
|
+
console.log(chalk_1.default.blue(`\nExecuting ${exportData.tests.length} tests...\n`));
|
|
483
|
+
}
|
|
484
|
+
const results = await (0, RestApiExecutor_1.executeTests)(exportData.tests, exportData.application.url, { verbose: options.verbose, client, applicationId: exportData.application.id },
|
|
485
|
+
// Test progress callback
|
|
486
|
+
(testResult, index, total) => {
|
|
487
|
+
if (!options.json) {
|
|
488
|
+
console.log((0, RestApiExecutor_1.formatTestResult)(testResult, options.verbose));
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
// Step progress callback (for verbose mode)
|
|
492
|
+
options.verbose ? (progress, stepResult) => {
|
|
493
|
+
// Already handled in formatTestResult
|
|
494
|
+
} : undefined);
|
|
495
|
+
// Calculate summary
|
|
496
|
+
const summary = (0, RestApiExecutor_1.calculateSummary)(results);
|
|
497
|
+
// Report results to backend
|
|
498
|
+
if (executionId) {
|
|
499
|
+
try {
|
|
500
|
+
await client.reportResults(executionId, results, summary, 'cli', {
|
|
501
|
+
os: os.platform(),
|
|
502
|
+
nodeVersion: process.version
|
|
503
|
+
});
|
|
504
|
+
if (!options.json) {
|
|
505
|
+
console.log(chalk_1.default.gray(`\nResults reported to Qate`));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
if (!options.json) {
|
|
510
|
+
console.log(chalk_1.default.yellow('Note: Could not report results to backend'));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Write JUnit XML report if requested
|
|
515
|
+
if (options.junit) {
|
|
516
|
+
try {
|
|
517
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXml)(results, exportData.testSet?.name || options.name);
|
|
518
|
+
const junitPath = path.resolve(options.junit);
|
|
519
|
+
const junitDir = path.dirname(junitPath);
|
|
520
|
+
if (!fs.existsSync(junitDir)) {
|
|
521
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
522
|
+
}
|
|
523
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
524
|
+
if (!options.json) {
|
|
525
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
if (!options.json) {
|
|
530
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Display final summary
|
|
535
|
+
if (options.json) {
|
|
536
|
+
console.log(JSON.stringify({
|
|
537
|
+
executionId,
|
|
538
|
+
testSetName: exportData.testSet?.name,
|
|
539
|
+
status: summary.status,
|
|
540
|
+
summary: {
|
|
541
|
+
total: summary.total,
|
|
542
|
+
passed: summary.passed,
|
|
543
|
+
failed: summary.failed,
|
|
544
|
+
error: summary.error
|
|
545
|
+
},
|
|
546
|
+
tests: results.map(r => ({
|
|
547
|
+
testId: r.testId,
|
|
548
|
+
testName: r.testName,
|
|
549
|
+
status: r.status,
|
|
550
|
+
duration: r.duration
|
|
551
|
+
}))
|
|
552
|
+
}, null, 2));
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
console.log();
|
|
556
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
557
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
558
|
+
console.log(` Total: ${summary.total}`);
|
|
559
|
+
console.log(` Passed: ${chalk_1.default.green(summary.passed.toString())}`);
|
|
560
|
+
console.log(` Failed: ${chalk_1.default.red(summary.failed.toString())}`);
|
|
561
|
+
console.log(` Error: ${chalk_1.default.red(summary.error.toString())}`);
|
|
562
|
+
console.log();
|
|
563
|
+
const statusColor = summary.status === 'passed' ? chalk_1.default.green :
|
|
564
|
+
summary.status === 'failed' ? chalk_1.default.red : chalk_1.default.red;
|
|
565
|
+
console.log(`Status: ${statusColor(summary.status.toUpperCase())}`);
|
|
566
|
+
}
|
|
567
|
+
process.exit(summary.status === 'passed' ? 0 : 1);
|
|
315
568
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
569
|
+
else if (appType === 'desktop') {
|
|
570
|
+
// Desktop apps: trigger server-side execution via connected agent, then poll
|
|
571
|
+
if (!options.json) {
|
|
572
|
+
console.log(chalk_1.default.cyan('Desktop application detected - executing via connected agent'));
|
|
573
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
574
|
+
}
|
|
575
|
+
const result = await client.executeTestSet(options.name, options.app);
|
|
576
|
+
if (!options.json) {
|
|
577
|
+
console.log(chalk_1.default.gray(`Execution ID: ${result.executionId}`));
|
|
578
|
+
console.log(chalk_1.default.blue('\nWaiting for execution to complete...\n'));
|
|
579
|
+
}
|
|
580
|
+
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
581
|
+
const status = await client.pollExecution(result.executionId, 'testset', timeoutMs);
|
|
582
|
+
// Write JUnit XML report if requested
|
|
583
|
+
if (options.junit) {
|
|
584
|
+
try {
|
|
585
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXmlFromStatus)(status, exportData.testSet?.name || options.name);
|
|
586
|
+
const junitPath = path.resolve(options.junit);
|
|
587
|
+
const junitDir = path.dirname(junitPath);
|
|
588
|
+
if (!fs.existsSync(junitDir)) {
|
|
589
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
590
|
+
}
|
|
591
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
592
|
+
if (!options.json) {
|
|
593
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
if (!options.json) {
|
|
598
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (options.json) {
|
|
603
|
+
console.log(JSON.stringify(status, null, 2));
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
displayStatus(status, 'testset');
|
|
607
|
+
}
|
|
608
|
+
process.exit(status.status === 'passed' ? 0 : 1);
|
|
320
609
|
}
|
|
321
610
|
else {
|
|
322
|
-
|
|
611
|
+
// Web apps should use 'qate generate' to create Playwright tests
|
|
612
|
+
console.error(chalk_1.default.red('\nError: "qate run" is not supported for web applications.'));
|
|
613
|
+
console.log();
|
|
614
|
+
console.log(chalk_1.default.yellow('For web applications, use "qate generate" to create Playwright tests:'));
|
|
615
|
+
console.log();
|
|
616
|
+
console.log(chalk_1.default.white(` qate generate -n "${options.name}" -o ./e2e`));
|
|
617
|
+
console.log(chalk_1.default.white(` cd ./e2e && npm install`));
|
|
618
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
619
|
+
console.log();
|
|
620
|
+
console.log(chalk_1.default.gray('This generates Playwright test files that you can run locally or in CI/CD.'));
|
|
621
|
+
console.log(chalk_1.default.gray('Results are automatically reported back to Qate.'));
|
|
622
|
+
process.exit(1);
|
|
323
623
|
}
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// Web apps should use 'qate generate' to create Playwright tests
|
|
328
|
-
console.error(chalk_1.default.red('\nError: "qate run" is not supported for web applications.'));
|
|
329
|
-
console.log();
|
|
330
|
-
console.log(chalk_1.default.yellow('For web applications, use "qate generate" to create Playwright tests:'));
|
|
331
|
-
console.log();
|
|
332
|
-
console.log(chalk_1.default.white(` qate generate -n "${options.name}" -o ./e2e`));
|
|
333
|
-
console.log(chalk_1.default.white(` cd ./e2e && npm install`));
|
|
334
|
-
console.log(chalk_1.default.white(` npx playwright test`));
|
|
335
|
-
console.log();
|
|
336
|
-
console.log(chalk_1.default.gray('This generates Playwright test files that you can run locally or in CI/CD.'));
|
|
337
|
-
console.log(chalk_1.default.gray('Results are automatically reported back to Qate.'));
|
|
338
|
-
process.exit(1);
|
|
339
|
-
}
|
|
624
|
+
} // end normal flow else
|
|
340
625
|
}
|
|
341
626
|
catch (error) {
|
|
342
627
|
handleApiError(error);
|
|
@@ -429,8 +714,7 @@ program
|
|
|
429
714
|
outputDir: options.output,
|
|
430
715
|
provider: 'local',
|
|
431
716
|
orchestration: 'websocket',
|
|
432
|
-
|
|
433
|
-
browser: 'chrome',
|
|
717
|
+
browser: 'chromium',
|
|
434
718
|
browserVersion: 'latest',
|
|
435
719
|
os: 'windows',
|
|
436
720
|
osVersion: '11',
|
|
@@ -485,6 +769,7 @@ program
|
|
|
485
769
|
.option('-w, --wait', 'Wait for execution to complete')
|
|
486
770
|
.option('--timeout <seconds>', 'Timeout for waiting (default: 600)', '600')
|
|
487
771
|
.option('--json', 'Output results as JSON')
|
|
772
|
+
.option('--junit <path>', 'Write JUnit XML report to file')
|
|
488
773
|
.option('-v, --verbose', 'Show detailed step-by-step output')
|
|
489
774
|
.action(async (options) => {
|
|
490
775
|
try {
|
|
@@ -515,6 +800,16 @@ program
|
|
|
515
800
|
}
|
|
516
801
|
}
|
|
517
802
|
catch (err) {
|
|
803
|
+
if (err.response?.status === 429 || err.response?.status === 402) {
|
|
804
|
+
const msg = err.response?.data?.message || 'Execution not allowed';
|
|
805
|
+
if (options.json) {
|
|
806
|
+
console.log(JSON.stringify({ error: err.response?.data?.error || 'limit_reached', message: msg }));
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
console.error(chalk_1.default.red(msg));
|
|
810
|
+
}
|
|
811
|
+
process.exit(1);
|
|
812
|
+
}
|
|
518
813
|
if (!options.json) {
|
|
519
814
|
console.log(chalk_1.default.yellow('Note: Could not create execution record'));
|
|
520
815
|
}
|
|
@@ -527,7 +822,7 @@ program
|
|
|
527
822
|
let stoppedEarly = false;
|
|
528
823
|
for (let i = 0; i < exportData.tests.length; i++) {
|
|
529
824
|
const test = exportData.tests[i];
|
|
530
|
-
const testResults = await (0, RestApiExecutor_1.executeTests)([test], exportData.application.url, { verbose: options.verbose }, (testResult) => {
|
|
825
|
+
const testResults = await (0, RestApiExecutor_1.executeTests)([test], exportData.application.url, { verbose: options.verbose, client, applicationId: exportData.application.id }, (testResult) => {
|
|
531
826
|
if (!options.json) {
|
|
532
827
|
console.log((0, RestApiExecutor_1.formatTestResult)(testResult, options.verbose));
|
|
533
828
|
}
|
|
@@ -561,6 +856,26 @@ program
|
|
|
561
856
|
}
|
|
562
857
|
}
|
|
563
858
|
}
|
|
859
|
+
// Write JUnit XML report if requested
|
|
860
|
+
if (options.junit) {
|
|
861
|
+
try {
|
|
862
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXml)(results, exportData.testSequence?.name || options.name);
|
|
863
|
+
const junitPath = path.resolve(options.junit);
|
|
864
|
+
const junitDir = path.dirname(junitPath);
|
|
865
|
+
if (!fs.existsSync(junitDir)) {
|
|
866
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
867
|
+
}
|
|
868
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
869
|
+
if (!options.json) {
|
|
870
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
if (!options.json) {
|
|
875
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
564
879
|
// Display final summary
|
|
565
880
|
if (options.json) {
|
|
566
881
|
console.log(JSON.stringify({
|
|
@@ -616,6 +931,26 @@ program
|
|
|
616
931
|
}
|
|
617
932
|
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
618
933
|
const status = await client.pollExecution(result.executionId, 'sequence', timeoutMs);
|
|
934
|
+
// Write JUnit XML report if requested
|
|
935
|
+
if (options.junit) {
|
|
936
|
+
try {
|
|
937
|
+
const junitXml = (0, JunitXmlGenerator_1.generateJunitXmlFromStatus)(status, exportData.testSequence?.name || options.name);
|
|
938
|
+
const junitPath = path.resolve(options.junit);
|
|
939
|
+
const junitDir = path.dirname(junitPath);
|
|
940
|
+
if (!fs.existsSync(junitDir)) {
|
|
941
|
+
fs.mkdirSync(junitDir, { recursive: true });
|
|
942
|
+
}
|
|
943
|
+
fs.writeFileSync(junitPath, junitXml, 'utf-8');
|
|
944
|
+
if (!options.json) {
|
|
945
|
+
console.log(chalk_1.default.gray(`JUnit XML report written to: ${junitPath}`));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
catch (err) {
|
|
949
|
+
if (!options.json) {
|
|
950
|
+
console.log(chalk_1.default.yellow(`Warning: Could not write JUnit XML report: ${err.message || err}`));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
619
954
|
if (options.json) {
|
|
620
955
|
console.log(JSON.stringify(status, null, 2));
|
|
621
956
|
}
|
|
@@ -729,8 +1064,7 @@ program
|
|
|
729
1064
|
outputDir: options.output,
|
|
730
1065
|
provider: 'local',
|
|
731
1066
|
orchestration: 'websocket',
|
|
732
|
-
|
|
733
|
-
browser: 'chrome',
|
|
1067
|
+
browser: 'chromium',
|
|
734
1068
|
browserVersion: 'latest',
|
|
735
1069
|
os: 'windows',
|
|
736
1070
|
osVersion: '11',
|
|
@@ -1018,39 +1352,88 @@ function writeAxiosFiles(files, outputDir, silent = false) {
|
|
|
1018
1352
|
program
|
|
1019
1353
|
.command('generate:testset')
|
|
1020
1354
|
.alias('generate')
|
|
1021
|
-
.description('Generate Playwright test files from a test set')
|
|
1022
|
-
.
|
|
1355
|
+
.description('Generate Playwright test files from a test set or AI-selected tests')
|
|
1356
|
+
.option('-n, --name <name>', 'Name of the test set')
|
|
1023
1357
|
.requiredOption('-o, --output <dir>', 'Output directory for generated files')
|
|
1024
1358
|
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
1025
1359
|
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
1026
|
-
.option('--app <applicationId>', 'Application ID (
|
|
1360
|
+
.option('--app <applicationId>', 'Application ID (required for --smart, optional otherwise)')
|
|
1361
|
+
.option('--smart', 'Use AI to select tests based on PR changes')
|
|
1362
|
+
.option('--pr <number>', 'PR number (auto-detected from CI environment if not specified)')
|
|
1363
|
+
.option('--repo-type <type>', 'Repository type for --smart: frontend or backend', 'frontend')
|
|
1027
1364
|
.option('--provider <provider>', 'Test provider: local, browserstack, saucelabs, lambdatest', 'local')
|
|
1028
1365
|
.option('--orchestration <mode>', 'Orchestration mode: websocket (run from CI) or cli (provider orchestrates)', 'websocket')
|
|
1029
|
-
.option('--
|
|
1030
|
-
.option('--browser <browser>', 'Browser for cloud providers: chrome, firefox, safari, edge', 'chrome')
|
|
1366
|
+
.option('--browser <browser>', 'Browser: chromium, firefox, webkit (local) or chrome, firefox, safari, edge (cloud)', 'chromium')
|
|
1031
1367
|
.option('--browser-version <version>', 'Browser version for cloud providers', 'latest')
|
|
1032
1368
|
.option('--os <os>', 'Operating system for cloud providers: windows, macos', 'windows')
|
|
1033
1369
|
.option('--os-version <version>', 'OS version for cloud providers (e.g., 11, Sonoma)', '11')
|
|
1370
|
+
.option('--device <name>', 'Playwright device name for mobile emulation (e.g. "iPhone 14", "Pixel 7")')
|
|
1034
1371
|
.option('--no-tracking', 'Disable result reporting to Qate')
|
|
1035
1372
|
.action(async (options) => {
|
|
1036
1373
|
try {
|
|
1374
|
+
// Validate: either --name or --smart is required
|
|
1375
|
+
if (!options.smart && !options.name) {
|
|
1376
|
+
console.error(chalk_1.default.red('Error: Either --name <test-set-name> or --smart is required.'));
|
|
1377
|
+
console.error(chalk_1.default.gray(' Use --name to generate from a specific test set'));
|
|
1378
|
+
console.error(chalk_1.default.gray(' Use --smart to let AI select tests based on PR changes'));
|
|
1379
|
+
process.exit(1);
|
|
1380
|
+
}
|
|
1381
|
+
if (options.smart && !options.app) {
|
|
1382
|
+
console.error(chalk_1.default.red('Error: --app <applicationId> is required when using --smart.'));
|
|
1383
|
+
console.error(chalk_1.default.gray(' Find your application ID in Qate: Settings > Applications'));
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
}
|
|
1037
1386
|
const { apiKey, baseUrl } = getConfig(options);
|
|
1038
1387
|
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
1039
|
-
|
|
1040
|
-
// Create execution record for tracking (unless disabled)
|
|
1388
|
+
let exportData;
|
|
1041
1389
|
let executionId;
|
|
1042
|
-
if (options.
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
console.
|
|
1390
|
+
if (options.smart) {
|
|
1391
|
+
// Smart generate: AI-powered test selection based on PR changes
|
|
1392
|
+
const prNumber = options.pr ? parseInt(options.pr) : detectPRFromEnvironment();
|
|
1393
|
+
if (!prNumber) {
|
|
1394
|
+
console.error(chalk_1.default.red('Error: Could not detect PR number from CI environment.'));
|
|
1395
|
+
console.error(chalk_1.default.gray(' Use --pr <number> to specify the PR number explicitly.'));
|
|
1396
|
+
console.error(chalk_1.default.gray(' Auto-detection supports: GitHub Actions, GitLab CI, Bitbucket Pipelines,'));
|
|
1397
|
+
console.error(chalk_1.default.gray(' Azure DevOps, Jenkins, CircleCI'));
|
|
1398
|
+
process.exit(1);
|
|
1047
1399
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1400
|
+
console.log(chalk_1.default.blue(`Smart generating tests for PR #${prNumber}...`));
|
|
1401
|
+
console.log(chalk_1.default.gray(` Application: ${options.app}`));
|
|
1402
|
+
console.log(chalk_1.default.gray(` Repository type: ${options.repoType}`));
|
|
1403
|
+
console.log(chalk_1.default.gray(' Analyzing PR changes with AI...'));
|
|
1404
|
+
exportData = await client.smartGenerate(options.app, prNumber, options.repoType);
|
|
1405
|
+
executionId = exportData.executionId;
|
|
1406
|
+
if (exportData.tests.length === 0) {
|
|
1407
|
+
console.log(chalk_1.default.yellow('\nAI analysis found no tests to execute for this PR.'));
|
|
1408
|
+
process.exit(0);
|
|
1409
|
+
}
|
|
1410
|
+
console.log(chalk_1.default.green(`\nAI selected ${exportData.tests.length} test(s) to execute`));
|
|
1411
|
+
if (executionId) {
|
|
1412
|
+
console.log(chalk_1.default.gray(` Execution ID: ${executionId}`));
|
|
1051
1413
|
}
|
|
1052
1414
|
}
|
|
1053
|
-
|
|
1415
|
+
else {
|
|
1416
|
+
// Normal flow: generate from named test set
|
|
1417
|
+
console.log(chalk_1.default.blue(`Fetching test set: ${options.name}`));
|
|
1418
|
+
// Create execution record for tracking (unless disabled)
|
|
1419
|
+
if (options.tracking !== false) {
|
|
1420
|
+
try {
|
|
1421
|
+
const generateResult = await client.createGenerateExecution(options.name, undefined, options.app, options.provider, options.browser, options.device);
|
|
1422
|
+
executionId = generateResult.executionId;
|
|
1423
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
1424
|
+
}
|
|
1425
|
+
catch (err) {
|
|
1426
|
+
if (err.response?.status === 429 || err.response?.status === 402) {
|
|
1427
|
+
const msg = err.response?.data?.message || 'Execution not allowed';
|
|
1428
|
+
console.error(chalk_1.default.red(msg));
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record for tracking'));
|
|
1432
|
+
console.log(chalk_1.default.gray(' Results will not be reported to Qate'));
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
exportData = await client.exportTestSet(options.name, options.app);
|
|
1436
|
+
}
|
|
1054
1437
|
// Show summary
|
|
1055
1438
|
console.log((0, PlaywrightGenerator_1.generateSummary)(exportData));
|
|
1056
1439
|
// Generate files with execution ID for result reporting
|
|
@@ -1058,13 +1441,13 @@ program
|
|
|
1058
1441
|
outputDir: options.output,
|
|
1059
1442
|
provider: options.provider,
|
|
1060
1443
|
orchestration: options.orchestration,
|
|
1061
|
-
browsers: options.browsers.split(',').map((b) => b.trim()),
|
|
1062
1444
|
browser: options.browser,
|
|
1063
1445
|
browserVersion: options.browserVersion,
|
|
1064
1446
|
os: options.os,
|
|
1065
1447
|
osVersion: options.osVersion,
|
|
1066
1448
|
executionId,
|
|
1067
1449
|
apiUrl: baseUrl,
|
|
1450
|
+
device: options.device,
|
|
1068
1451
|
};
|
|
1069
1452
|
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
1070
1453
|
// Write files
|
|
@@ -1119,11 +1502,11 @@ program
|
|
|
1119
1502
|
.option('--app <applicationId>', 'Application ID (if name is not unique)')
|
|
1120
1503
|
.option('--provider <provider>', 'Test provider: local, browserstack, saucelabs, lambdatest', 'local')
|
|
1121
1504
|
.option('--orchestration <mode>', 'Orchestration mode: websocket (run from CI) or cli (provider orchestrates)', 'websocket')
|
|
1122
|
-
.option('--
|
|
1123
|
-
.option('--browser <browser>', 'Browser for cloud providers: chrome, firefox, safari, edge', 'chrome')
|
|
1505
|
+
.option('--browser <browser>', 'Browser: chromium, firefox, webkit (local) or chrome, firefox, safari, edge (cloud)', 'chromium')
|
|
1124
1506
|
.option('--browser-version <version>', 'Browser version for cloud providers', 'latest')
|
|
1125
1507
|
.option('--os <os>', 'Operating system for cloud providers: windows, macos', 'windows')
|
|
1126
1508
|
.option('--os-version <version>', 'OS version for cloud providers (e.g., 11, Sonoma)', '11')
|
|
1509
|
+
.option('--device <name>', 'Playwright device name for mobile emulation (e.g. "iPhone 14", "Pixel 7")')
|
|
1127
1510
|
.option('--no-tracking', 'Disable result reporting to Qate')
|
|
1128
1511
|
.action(async (options) => {
|
|
1129
1512
|
try {
|
|
@@ -1134,11 +1517,16 @@ program
|
|
|
1134
1517
|
let executionId;
|
|
1135
1518
|
if (options.tracking !== false) {
|
|
1136
1519
|
try {
|
|
1137
|
-
const generateResult = await client.createGenerateExecution(undefined, options.name, options.app, options.provider);
|
|
1520
|
+
const generateResult = await client.createGenerateExecution(undefined, options.name, options.app, options.provider, options.browser, options.device);
|
|
1138
1521
|
executionId = generateResult.executionId;
|
|
1139
1522
|
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
1140
1523
|
}
|
|
1141
1524
|
catch (err) {
|
|
1525
|
+
if (err.response?.status === 429 || err.response?.status === 402) {
|
|
1526
|
+
const msg = err.response?.data?.message || 'Execution not allowed';
|
|
1527
|
+
console.error(chalk_1.default.red(msg));
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1142
1530
|
console.log(chalk_1.default.yellow('Note: Could not create execution record for tracking'));
|
|
1143
1531
|
console.log(chalk_1.default.gray(' Results will not be reported to Qate'));
|
|
1144
1532
|
}
|
|
@@ -1151,13 +1539,13 @@ program
|
|
|
1151
1539
|
outputDir: options.output,
|
|
1152
1540
|
provider: options.provider,
|
|
1153
1541
|
orchestration: options.orchestration,
|
|
1154
|
-
browsers: options.browsers.split(',').map((b) => b.trim()),
|
|
1155
1542
|
browser: options.browser,
|
|
1156
1543
|
browserVersion: options.browserVersion,
|
|
1157
1544
|
os: options.os,
|
|
1158
1545
|
osVersion: options.osVersion,
|
|
1159
1546
|
executionId,
|
|
1160
1547
|
apiUrl: baseUrl,
|
|
1548
|
+
device: options.device,
|
|
1161
1549
|
};
|
|
1162
1550
|
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
1163
1551
|
// Write files
|