@qate/cli 1.0.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 -0
- package/dist/AxiosGenerator.d.ts +24 -0
- package/dist/AxiosGenerator.d.ts.map +1 -0
- package/dist/AxiosGenerator.js +260 -0
- package/dist/AxiosGenerator.js.map +1 -0
- package/dist/PlaywrightGenerator.d.ts +39 -0
- package/dist/PlaywrightGenerator.d.ts.map +1 -0
- package/dist/PlaywrightGenerator.js +1342 -0
- package/dist/PlaywrightGenerator.js.map +1 -0
- package/dist/RestApiExecutor.d.ts +45 -0
- package/dist/RestApiExecutor.d.ts.map +1 -0
- package/dist/RestApiExecutor.js +336 -0
- package/dist/RestApiExecutor.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1269 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +296 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +143 -0
- package/dist/client.js.map +1 -0
- package/package.json +71 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const client_1 = require("./client");
|
|
43
|
+
const PlaywrightGenerator_1 = require("./PlaywrightGenerator");
|
|
44
|
+
const AxiosGenerator_1 = require("./AxiosGenerator");
|
|
45
|
+
const RestApiExecutor_1 = require("./RestApiExecutor");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const VERSION = '1.0.0';
|
|
50
|
+
const program = new commander_1.Command();
|
|
51
|
+
// Helper to handle API errors with detailed feedback
|
|
52
|
+
function handleApiError(error) {
|
|
53
|
+
const status = error.response?.status;
|
|
54
|
+
const data = error.response?.data;
|
|
55
|
+
// Handle 409 Conflict - ambiguous name (multiple matches)
|
|
56
|
+
if (status === 409 && data?.matches) {
|
|
57
|
+
console.error(chalk_1.default.red(`Error: ${data.message || 'Ambiguous name'}`));
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk_1.default.yellow('Available options:'));
|
|
60
|
+
console.log(chalk_1.default.gray('─────────────────────────────────────'));
|
|
61
|
+
data.matches.forEach((match, idx) => {
|
|
62
|
+
const appInfo = match.application;
|
|
63
|
+
console.log(` ${idx + 1}. ${chalk_1.default.white(match.name)}`);
|
|
64
|
+
console.log(` Application: ${chalk_1.default.cyan(appInfo.name)} (${appInfo.type})`);
|
|
65
|
+
console.log(` Use: ${chalk_1.default.green(`--app ${appInfo.id}`)}`);
|
|
66
|
+
});
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk_1.default.gray('Example:'));
|
|
69
|
+
if (data.matches.length > 0) {
|
|
70
|
+
const firstMatch = data.matches[0];
|
|
71
|
+
console.log(chalk_1.default.white(` qate run -n "${firstMatch.name}" --app ${firstMatch.application.id}`));
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Handle 404 Not Found
|
|
76
|
+
if (status === 404) {
|
|
77
|
+
console.error(chalk_1.default.red(`Error: ${data?.message || 'Not found'}`));
|
|
78
|
+
console.log(chalk_1.default.gray('Check the name and try again, or run "qate list" to see available items.'));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
// Handle 401 Unauthorized
|
|
82
|
+
if (status === 401) {
|
|
83
|
+
console.error(chalk_1.default.red(`Error: ${data?.message || 'Authentication failed'}`));
|
|
84
|
+
console.log(chalk_1.default.gray('Check your API key and try again.'));
|
|
85
|
+
console.log(chalk_1.default.gray('Generate a new key at: Settings > CI/CD API Keys'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
// Handle 429 Rate Limit - monthly execution limit reached
|
|
89
|
+
if (status === 429) {
|
|
90
|
+
console.error(chalk_1.default.red(`Error: ${data?.message || 'Monthly execution limit reached'}`));
|
|
91
|
+
if (data?.current !== undefined && data?.limit !== undefined) {
|
|
92
|
+
console.log(chalk_1.default.gray(`Usage: ${data.current}/${data.limit} executions this month`));
|
|
93
|
+
}
|
|
94
|
+
console.log(chalk_1.default.gray('Upgrade your plan at: https://app.qate.ai/settings/billing'));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// Handle 403 Forbidden - plan restriction
|
|
98
|
+
if (status === 403) {
|
|
99
|
+
console.error(chalk_1.default.red(`Error: ${data?.message || 'Access denied'}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
// Handle 400 Bad Request
|
|
103
|
+
if (status === 400) {
|
|
104
|
+
console.error(chalk_1.default.red(`Error: ${data?.message || 'Bad request'}`));
|
|
105
|
+
if (data?.error) {
|
|
106
|
+
console.log(chalk_1.default.gray(`Details: ${data.error}`));
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
// Generic error handling
|
|
111
|
+
console.error(chalk_1.default.red('Error:'), data?.message || error.message);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
// Helper to get API key and base URL
|
|
115
|
+
function getConfig(options) {
|
|
116
|
+
const apiKey = options.apiKey || process.env.QATE_API_KEY || process.env.TESTMIND_API_KEY;
|
|
117
|
+
const baseUrl = options.url || process.env.QATE_API_URL || process.env.TESTMIND_API_URL || 'https://api.qate.ai';
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
console.error(chalk_1.default.red('Error: API key is required.'));
|
|
120
|
+
console.error(chalk_1.default.gray('Use --api-key option or set QATE_API_KEY environment variable.'));
|
|
121
|
+
console.error(chalk_1.default.gray('Generate an API key at: Settings > CI/CD API Keys'));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (!apiKey.startsWith('qate_')) {
|
|
125
|
+
console.error(chalk_1.default.yellow('Warning: API key should start with "qate_". Legacy keys are no longer supported.'));
|
|
126
|
+
}
|
|
127
|
+
return { apiKey, baseUrl };
|
|
128
|
+
}
|
|
129
|
+
// Helper to display execution status
|
|
130
|
+
function displayStatus(status, type) {
|
|
131
|
+
const name = type === 'testset' ? status.testSetName : status.sequenceName;
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk_1.default.blue(`Execution Status`));
|
|
134
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
135
|
+
console.log(` Execution ID: ${chalk_1.default.cyan(status.executionId)}`);
|
|
136
|
+
console.log(` ${type === 'testset' ? 'Test Set' : 'Sequence'}: ${chalk_1.default.white(name)}`);
|
|
137
|
+
const statusColor = status.status === 'passed' ? chalk_1.default.green :
|
|
138
|
+
status.status === 'failed' ? chalk_1.default.red :
|
|
139
|
+
status.status === 'running' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
140
|
+
console.log(` Status: ${statusColor(status.status.toUpperCase())}`);
|
|
141
|
+
console.log();
|
|
142
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
143
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
144
|
+
console.log(` Total: ${status.summary.total}`);
|
|
145
|
+
console.log(` Passed: ${chalk_1.default.green(status.summary.passed.toString())}`);
|
|
146
|
+
console.log(` Failed: ${chalk_1.default.red(status.summary.failed.toString())}`);
|
|
147
|
+
console.log(` Error: ${chalk_1.default.red(status.summary.error.toString())}`);
|
|
148
|
+
console.log(` Pending: ${chalk_1.default.yellow(status.summary.pending.toString())}`);
|
|
149
|
+
if (status.stoppedAt !== undefined && status.stoppedAt !== null) {
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk_1.default.yellow(`Execution stopped at test #${status.stoppedAt + 1}`));
|
|
152
|
+
if (status.stoppedReason) {
|
|
153
|
+
console.log(chalk_1.default.yellow(`Reason: ${status.stoppedReason}`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (status.tests && status.tests.length > 0) {
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(chalk_1.default.blue(`Test Results`));
|
|
159
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
160
|
+
status.tests.forEach((test, idx) => {
|
|
161
|
+
const icon = test.status === 'passed' ? chalk_1.default.green('✓') :
|
|
162
|
+
test.status === 'failed' ? chalk_1.default.red('✗') :
|
|
163
|
+
test.status === 'error' ? chalk_1.default.red('!') : chalk_1.default.gray('○');
|
|
164
|
+
const time = test.executionTime ? `(${(test.executionTime / 1000).toFixed(1)}s)` : '';
|
|
165
|
+
console.log(` ${icon} ${test.testName} ${chalk_1.default.gray(time)}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
console.log(chalk_1.default.gray(`Last updated: ${status.lastUpdated}`));
|
|
170
|
+
}
|
|
171
|
+
program
|
|
172
|
+
.name('qate')
|
|
173
|
+
.description('Qate CLI for CI/CD pipeline integration\n\nGenerate and run Playwright tests from your Qate test definitions.')
|
|
174
|
+
.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)
|
|
189
|
+
`);
|
|
190
|
+
// ================== TEST SET COMMANDS ==================
|
|
191
|
+
program
|
|
192
|
+
.command('run:testset')
|
|
193
|
+
.alias('run')
|
|
194
|
+
.description('Execute a test set locally (REST API and SOAP only - use "generate" for web apps)')
|
|
195
|
+
.requiredOption('-n, --name <name>', 'Name of the test set to execute')
|
|
196
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
197
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
198
|
+
.option('--app <applicationId>', 'Application ID (if test set name is not unique)')
|
|
199
|
+
.option('-w, --wait', 'Wait for execution to complete')
|
|
200
|
+
.option('--timeout <seconds>', 'Timeout for waiting (default: 600)', '600')
|
|
201
|
+
.option('--json', 'Output results as JSON')
|
|
202
|
+
.option('-v, --verbose', 'Show detailed step-by-step output')
|
|
203
|
+
.action(async (options) => {
|
|
204
|
+
try {
|
|
205
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
206
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
207
|
+
if (!options.json) {
|
|
208
|
+
console.log(chalk_1.default.blue(`Fetching test set: ${options.name}`));
|
|
209
|
+
}
|
|
210
|
+
// First, export the test set to check application type
|
|
211
|
+
const exportData = await client.exportTestSet(options.name, options.app);
|
|
212
|
+
const appType = exportData.application?.type || 'web';
|
|
213
|
+
// REST API apps execute locally in the CLI
|
|
214
|
+
if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
215
|
+
if (!options.json) {
|
|
216
|
+
console.log(chalk_1.default.cyan(`REST API application detected - executing locally`));
|
|
217
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
218
|
+
console.log(chalk_1.default.gray(`Base URL: ${exportData.application.url}`));
|
|
219
|
+
console.log(chalk_1.default.gray(`Tests: ${exportData.tests.length}`));
|
|
220
|
+
console.log();
|
|
221
|
+
}
|
|
222
|
+
// Create execution record for tracking
|
|
223
|
+
let executionId;
|
|
224
|
+
try {
|
|
225
|
+
const generateResult = await client.createGenerateExecution(options.name, undefined, options.app, 'cli');
|
|
226
|
+
executionId = generateResult.executionId;
|
|
227
|
+
if (!options.json) {
|
|
228
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if (!options.json) {
|
|
233
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record'));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Execute tests locally
|
|
237
|
+
if (!options.json) {
|
|
238
|
+
console.log(chalk_1.default.blue(`\nExecuting ${exportData.tests.length} tests...\n`));
|
|
239
|
+
}
|
|
240
|
+
const results = await (0, RestApiExecutor_1.executeTests)(exportData.tests, exportData.application.url, { verbose: options.verbose },
|
|
241
|
+
// Test progress callback
|
|
242
|
+
(testResult, index, total) => {
|
|
243
|
+
if (!options.json) {
|
|
244
|
+
console.log((0, RestApiExecutor_1.formatTestResult)(testResult, options.verbose));
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
// Step progress callback (for verbose mode)
|
|
248
|
+
options.verbose ? (progress, stepResult) => {
|
|
249
|
+
// Already handled in formatTestResult
|
|
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
|
+
});
|
|
260
|
+
if (!options.json) {
|
|
261
|
+
console.log(chalk_1.default.gray(`\nResults reported to Qate`));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
if (!options.json) {
|
|
266
|
+
console.log(chalk_1.default.yellow('Note: Could not report results to backend'));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Display final summary
|
|
271
|
+
if (options.json) {
|
|
272
|
+
console.log(JSON.stringify({
|
|
273
|
+
executionId,
|
|
274
|
+
testSetName: exportData.testSet?.name,
|
|
275
|
+
status: summary.status,
|
|
276
|
+
summary: {
|
|
277
|
+
total: summary.total,
|
|
278
|
+
passed: summary.passed,
|
|
279
|
+
failed: summary.failed,
|
|
280
|
+
error: summary.error
|
|
281
|
+
},
|
|
282
|
+
tests: results.map(r => ({
|
|
283
|
+
testId: r.testId,
|
|
284
|
+
testName: r.testName,
|
|
285
|
+
status: r.status,
|
|
286
|
+
duration: r.duration
|
|
287
|
+
}))
|
|
288
|
+
}, null, 2));
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
console.log();
|
|
292
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
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())}`);
|
|
298
|
+
console.log();
|
|
299
|
+
const statusColor = summary.status === 'passed' ? chalk_1.default.green :
|
|
300
|
+
summary.status === 'failed' ? chalk_1.default.red : chalk_1.default.red;
|
|
301
|
+
console.log(`Status: ${statusColor(summary.status.toUpperCase())}`);
|
|
302
|
+
}
|
|
303
|
+
process.exit(summary.status === 'passed' ? 0 : 1);
|
|
304
|
+
}
|
|
305
|
+
else if (appType === 'desktop') {
|
|
306
|
+
// Desktop apps: trigger server-side execution via connected agent, then poll
|
|
307
|
+
if (!options.json) {
|
|
308
|
+
console.log(chalk_1.default.cyan('Desktop application detected - executing via connected agent'));
|
|
309
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
310
|
+
}
|
|
311
|
+
const result = await client.executeTestSet(options.name, options.app);
|
|
312
|
+
if (!options.json) {
|
|
313
|
+
console.log(chalk_1.default.gray(`Execution ID: ${result.executionId}`));
|
|
314
|
+
console.log(chalk_1.default.blue('\nWaiting for execution to complete...\n'));
|
|
315
|
+
}
|
|
316
|
+
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
317
|
+
const status = await client.pollExecution(result.executionId, 'testset', timeoutMs);
|
|
318
|
+
if (options.json) {
|
|
319
|
+
console.log(JSON.stringify(status, null, 2));
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
displayStatus(status, 'testset');
|
|
323
|
+
}
|
|
324
|
+
process.exit(status.status === 'passed' ? 0 : 1);
|
|
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
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
handleApiError(error);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
program
|
|
346
|
+
.command('status:testset')
|
|
347
|
+
.description('Get status of a test set execution')
|
|
348
|
+
.requiredOption('-e, --execution-id <id>', 'Execution ID')
|
|
349
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
350
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
351
|
+
.option('--json', 'Output as JSON')
|
|
352
|
+
.action(async (options) => {
|
|
353
|
+
try {
|
|
354
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
355
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
356
|
+
const status = await client.getTestSetStatus(options.executionId);
|
|
357
|
+
if (options.json) {
|
|
358
|
+
console.log(JSON.stringify(status, null, 2));
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
displayStatus(status, 'testset');
|
|
362
|
+
}
|
|
363
|
+
process.exit(status.status === 'passed' ? 0 : status.status === 'running' ? 0 : 1);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
handleApiError(error);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
program
|
|
370
|
+
.command('list:testsets')
|
|
371
|
+
.alias('list')
|
|
372
|
+
.description('List all test sets')
|
|
373
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
374
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
375
|
+
.option('--json', 'Output as JSON')
|
|
376
|
+
.action(async (options) => {
|
|
377
|
+
try {
|
|
378
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
379
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
380
|
+
const result = await client.listTestSets();
|
|
381
|
+
if (options.json) {
|
|
382
|
+
console.log(JSON.stringify(result, null, 2));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
console.log(chalk_1.default.blue(`\nTest Sets (${result.testSets.length})`));
|
|
386
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
387
|
+
if (result.testSets.length === 0) {
|
|
388
|
+
console.log(chalk_1.default.gray(' No test sets found'));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
result.testSets.forEach((testSet) => {
|
|
392
|
+
const statusIcon = testSet.lastExecutionStatus === 'passed' ? chalk_1.default.green('✓') :
|
|
393
|
+
testSet.lastExecutionStatus === 'failed' ? chalk_1.default.red('✗') :
|
|
394
|
+
chalk_1.default.gray('○');
|
|
395
|
+
console.log(`\n ${statusIcon} ${chalk_1.default.white(testSet.name)}`);
|
|
396
|
+
if (testSet.description) {
|
|
397
|
+
console.log(chalk_1.default.gray(` ${testSet.description}`));
|
|
398
|
+
}
|
|
399
|
+
console.log(chalk_1.default.gray(` App: ${testSet.application.name} | Tests: ${testSet.testCount}`));
|
|
400
|
+
if (testSet.lastExecuted) {
|
|
401
|
+
console.log(chalk_1.default.gray(` Last run: ${new Date(testSet.lastExecuted).toLocaleString()}`));
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
console.log();
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
handleApiError(error);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
program
|
|
411
|
+
.command('export:testset')
|
|
412
|
+
.description('Export a test set as runnable test code (Playwright for web apps, Axios for REST/SOAP APIs)')
|
|
413
|
+
.requiredOption('-n, --name <name>', 'Name of the test set')
|
|
414
|
+
.requiredOption('-o, --output <dir>', 'Output directory for generated files')
|
|
415
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
416
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
417
|
+
.option('--app <applicationId>', 'Application ID (if name is not unique)')
|
|
418
|
+
.action(async (options) => {
|
|
419
|
+
try {
|
|
420
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
421
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
422
|
+
console.log(chalk_1.default.blue(`Fetching test set: ${options.name}`));
|
|
423
|
+
const exportData = await client.exportTestSet(options.name, options.app);
|
|
424
|
+
const appType = exportData.application?.type || 'web';
|
|
425
|
+
if (appType === 'web') {
|
|
426
|
+
// Web apps → Playwright tests
|
|
427
|
+
console.log((0, PlaywrightGenerator_1.generateSummary)(exportData));
|
|
428
|
+
const generatorOptions = {
|
|
429
|
+
outputDir: options.output,
|
|
430
|
+
provider: 'local',
|
|
431
|
+
orchestration: 'websocket',
|
|
432
|
+
browsers: ['chromium'],
|
|
433
|
+
browser: 'chrome',
|
|
434
|
+
browserVersion: 'latest',
|
|
435
|
+
os: 'windows',
|
|
436
|
+
osVersion: '11',
|
|
437
|
+
apiUrl: baseUrl,
|
|
438
|
+
};
|
|
439
|
+
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
440
|
+
writeGeneratedFiles(files, options.output);
|
|
441
|
+
console.log(chalk_1.default.green('\nPlaywright tests exported successfully!'));
|
|
442
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
443
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
444
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
445
|
+
console.log(chalk_1.default.white(` npx playwright install`));
|
|
446
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
447
|
+
}
|
|
448
|
+
else if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
449
|
+
// REST API / SOAP → Axios tests
|
|
450
|
+
console.log((0, AxiosGenerator_1.generateAxiosSummary)(exportData));
|
|
451
|
+
const axiosOptions = {
|
|
452
|
+
outputDir: options.output,
|
|
453
|
+
};
|
|
454
|
+
const files = (0, AxiosGenerator_1.generateAxiosTests)(exportData, axiosOptions);
|
|
455
|
+
writeAxiosFiles(files, options.output);
|
|
456
|
+
console.log(chalk_1.default.green('\nAxios tests exported successfully!'));
|
|
457
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
458
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
459
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
460
|
+
console.log(chalk_1.default.white(` npx vitest run`));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// Desktop or unsupported type
|
|
464
|
+
console.error(chalk_1.default.red(`\nError: Export is not supported for ${appType} applications.`));
|
|
465
|
+
console.log();
|
|
466
|
+
console.log(chalk_1.default.gray('Export currently supports:'));
|
|
467
|
+
console.log(chalk_1.default.white(' - Web applications (exports Playwright tests)'));
|
|
468
|
+
console.log(chalk_1.default.white(' - REST API applications (exports Axios tests)'));
|
|
469
|
+
console.log(chalk_1.default.white(' - SOAP services (exports Axios tests)'));
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
handleApiError(error);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
// ================== TEST SEQUENCE COMMANDS ==================
|
|
478
|
+
program
|
|
479
|
+
.command('run:sequence')
|
|
480
|
+
.description('Execute a test sequence locally (REST API and SOAP only - use "generate:sequence" for web apps)')
|
|
481
|
+
.requiredOption('-n, --name <name>', 'Name of the test sequence to execute')
|
|
482
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
483
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
484
|
+
.option('--app <applicationId>', 'Application ID (if sequence name is not unique)')
|
|
485
|
+
.option('-w, --wait', 'Wait for execution to complete')
|
|
486
|
+
.option('--timeout <seconds>', 'Timeout for waiting (default: 600)', '600')
|
|
487
|
+
.option('--json', 'Output results as JSON')
|
|
488
|
+
.option('-v, --verbose', 'Show detailed step-by-step output')
|
|
489
|
+
.action(async (options) => {
|
|
490
|
+
try {
|
|
491
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
492
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
493
|
+
if (!options.json) {
|
|
494
|
+
console.log(chalk_1.default.blue(`Fetching test sequence: ${options.name}`));
|
|
495
|
+
}
|
|
496
|
+
// First, export the test sequence to check application type
|
|
497
|
+
const exportData = await client.exportTestSequence(options.name, options.app);
|
|
498
|
+
const appType = exportData.application?.type || 'web';
|
|
499
|
+
// REST API apps execute locally in the CLI
|
|
500
|
+
if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
501
|
+
if (!options.json) {
|
|
502
|
+
console.log(chalk_1.default.cyan(`REST API application detected - executing locally`));
|
|
503
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
504
|
+
console.log(chalk_1.default.gray(`Base URL: ${exportData.application.url}`));
|
|
505
|
+
console.log(chalk_1.default.gray(`Tests: ${exportData.tests.length} (sequential)`));
|
|
506
|
+
console.log();
|
|
507
|
+
}
|
|
508
|
+
// Create execution record for tracking
|
|
509
|
+
let executionId;
|
|
510
|
+
try {
|
|
511
|
+
const generateResult = await client.createGenerateExecution(undefined, options.name, options.app, 'cli');
|
|
512
|
+
executionId = generateResult.executionId;
|
|
513
|
+
if (!options.json) {
|
|
514
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (err) {
|
|
518
|
+
if (!options.json) {
|
|
519
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record'));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Execute tests locally (sequentially - stop on first failure)
|
|
523
|
+
if (!options.json) {
|
|
524
|
+
console.log(chalk_1.default.blue(`\nExecuting ${exportData.tests.length} tests sequentially...\n`));
|
|
525
|
+
}
|
|
526
|
+
const results = [];
|
|
527
|
+
let stoppedEarly = false;
|
|
528
|
+
for (let i = 0; i < exportData.tests.length; i++) {
|
|
529
|
+
const test = exportData.tests[i];
|
|
530
|
+
const testResults = await (0, RestApiExecutor_1.executeTests)([test], exportData.application.url, { verbose: options.verbose }, (testResult) => {
|
|
531
|
+
if (!options.json) {
|
|
532
|
+
console.log((0, RestApiExecutor_1.formatTestResult)(testResult, options.verbose));
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
results.push(...testResults);
|
|
536
|
+
// Stop on failure for sequences (tests are dependent)
|
|
537
|
+
if (testResults[0].status !== 'passed') {
|
|
538
|
+
stoppedEarly = true;
|
|
539
|
+
if (!options.json) {
|
|
540
|
+
console.log(chalk_1.default.yellow(`\nSequence stopped at test ${i + 1} due to failure`));
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// Calculate summary
|
|
546
|
+
const summary = (0, RestApiExecutor_1.calculateSummary)(results);
|
|
547
|
+
// Report results to backend
|
|
548
|
+
if (executionId) {
|
|
549
|
+
try {
|
|
550
|
+
await client.reportResults(executionId, results, summary, 'cli', {
|
|
551
|
+
os: os.platform(),
|
|
552
|
+
nodeVersion: process.version
|
|
553
|
+
});
|
|
554
|
+
if (!options.json) {
|
|
555
|
+
console.log(chalk_1.default.gray(`\nResults reported to Qate`));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (!options.json) {
|
|
560
|
+
console.log(chalk_1.default.yellow('Note: Could not report results to backend'));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Display final summary
|
|
565
|
+
if (options.json) {
|
|
566
|
+
console.log(JSON.stringify({
|
|
567
|
+
executionId,
|
|
568
|
+
testSequenceName: exportData.testSequence?.name,
|
|
569
|
+
status: summary.status,
|
|
570
|
+
stoppedEarly,
|
|
571
|
+
summary: {
|
|
572
|
+
total: exportData.tests.length,
|
|
573
|
+
executed: summary.total,
|
|
574
|
+
passed: summary.passed,
|
|
575
|
+
failed: summary.failed,
|
|
576
|
+
error: summary.error,
|
|
577
|
+
skipped: exportData.tests.length - summary.total
|
|
578
|
+
},
|
|
579
|
+
tests: results.map(r => ({
|
|
580
|
+
testId: r.testId,
|
|
581
|
+
testName: r.testName,
|
|
582
|
+
status: r.status,
|
|
583
|
+
duration: r.duration
|
|
584
|
+
}))
|
|
585
|
+
}, null, 2));
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
console.log();
|
|
589
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
590
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
591
|
+
console.log(` Total: ${exportData.tests.length}`);
|
|
592
|
+
console.log(` Executed: ${summary.total}`);
|
|
593
|
+
console.log(` Passed: ${chalk_1.default.green(summary.passed.toString())}`);
|
|
594
|
+
console.log(` Failed: ${chalk_1.default.red(summary.failed.toString())}`);
|
|
595
|
+
console.log(` Error: ${chalk_1.default.red(summary.error.toString())}`);
|
|
596
|
+
if (stoppedEarly) {
|
|
597
|
+
console.log(` Skipped: ${chalk_1.default.yellow((exportData.tests.length - summary.total).toString())}`);
|
|
598
|
+
}
|
|
599
|
+
console.log();
|
|
600
|
+
const statusColor = summary.status === 'passed' ? chalk_1.default.green :
|
|
601
|
+
summary.status === 'failed' ? chalk_1.default.red : chalk_1.default.red;
|
|
602
|
+
console.log(`Status: ${statusColor(summary.status.toUpperCase())}`);
|
|
603
|
+
}
|
|
604
|
+
process.exit(summary.status === 'passed' ? 0 : 1);
|
|
605
|
+
}
|
|
606
|
+
else if (appType === 'desktop') {
|
|
607
|
+
// Desktop apps: trigger server-side execution via connected agent, then poll
|
|
608
|
+
if (!options.json) {
|
|
609
|
+
console.log(chalk_1.default.cyan('Desktop application detected - executing via connected agent'));
|
|
610
|
+
console.log(chalk_1.default.gray(`Application: ${exportData.application.name}`));
|
|
611
|
+
}
|
|
612
|
+
const result = await client.executeTestSequence(options.name, options.app);
|
|
613
|
+
if (!options.json) {
|
|
614
|
+
console.log(chalk_1.default.gray(`Execution ID: ${result.executionId}`));
|
|
615
|
+
console.log(chalk_1.default.blue('\nWaiting for execution to complete...\n'));
|
|
616
|
+
}
|
|
617
|
+
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
618
|
+
const status = await client.pollExecution(result.executionId, 'sequence', timeoutMs);
|
|
619
|
+
if (options.json) {
|
|
620
|
+
console.log(JSON.stringify(status, null, 2));
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
displayStatus(status, 'sequence');
|
|
624
|
+
}
|
|
625
|
+
process.exit(status.status === 'passed' ? 0 : 1);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// Web apps should use 'qate generate:sequence' to create Playwright tests
|
|
629
|
+
console.error(chalk_1.default.red('\nError: "qate run:sequence" is not supported for web applications.'));
|
|
630
|
+
console.log();
|
|
631
|
+
console.log(chalk_1.default.yellow('For web applications, use "qate generate:sequence" to create Playwright tests:'));
|
|
632
|
+
console.log();
|
|
633
|
+
console.log(chalk_1.default.white(` qate generate:sequence -n "${options.name}" -o ./e2e`));
|
|
634
|
+
console.log(chalk_1.default.white(` cd ./e2e && npm install`));
|
|
635
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
636
|
+
console.log();
|
|
637
|
+
console.log(chalk_1.default.gray('This generates Playwright test files that you can run locally or in CI/CD.'));
|
|
638
|
+
console.log(chalk_1.default.gray('Results are automatically reported back to Qate.'));
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
handleApiError(error);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
program
|
|
647
|
+
.command('status:sequence')
|
|
648
|
+
.description('Get status of a test sequence execution')
|
|
649
|
+
.requiredOption('-e, --execution-id <id>', 'Execution ID')
|
|
650
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
651
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
652
|
+
.option('--json', 'Output as JSON')
|
|
653
|
+
.action(async (options) => {
|
|
654
|
+
try {
|
|
655
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
656
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
657
|
+
const status = await client.getTestSequenceStatus(options.executionId);
|
|
658
|
+
if (options.json) {
|
|
659
|
+
console.log(JSON.stringify(status, null, 2));
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
displayStatus(status, 'sequence');
|
|
663
|
+
}
|
|
664
|
+
process.exit(status.status === 'passed' ? 0 : status.status === 'running' ? 0 : 1);
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
handleApiError(error);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
program
|
|
671
|
+
.command('list:sequences')
|
|
672
|
+
.description('List all test sequences')
|
|
673
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
674
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
675
|
+
.option('--json', 'Output as JSON')
|
|
676
|
+
.action(async (options) => {
|
|
677
|
+
try {
|
|
678
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
679
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
680
|
+
const result = await client.listTestSequences();
|
|
681
|
+
if (options.json) {
|
|
682
|
+
console.log(JSON.stringify(result, null, 2));
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
console.log(chalk_1.default.blue(`\nTest Sequences (${result.testSequences.length})`));
|
|
686
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
687
|
+
if (result.testSequences.length === 0) {
|
|
688
|
+
console.log(chalk_1.default.gray(' No test sequences found'));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
result.testSequences.forEach((seq) => {
|
|
692
|
+
const statusIcon = seq.lastExecutionStatus === 'passed' ? chalk_1.default.green('✓') :
|
|
693
|
+
seq.lastExecutionStatus === 'failed' ? chalk_1.default.red('✗') :
|
|
694
|
+
chalk_1.default.gray('○');
|
|
695
|
+
console.log(`\n ${statusIcon} ${chalk_1.default.white(seq.name)}`);
|
|
696
|
+
if (seq.description) {
|
|
697
|
+
console.log(chalk_1.default.gray(` ${seq.description}`));
|
|
698
|
+
}
|
|
699
|
+
console.log(chalk_1.default.gray(` App: ${seq.application.name} | Tests: ${seq.testCount} (sequential)`));
|
|
700
|
+
if (seq.lastExecuted) {
|
|
701
|
+
console.log(chalk_1.default.gray(` Last run: ${new Date(seq.lastExecuted).toLocaleString()}`));
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
console.log();
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
handleApiError(error);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
program
|
|
711
|
+
.command('export:sequence')
|
|
712
|
+
.description('Export a test sequence as runnable test code (Playwright for web apps, Axios for REST/SOAP APIs)')
|
|
713
|
+
.requiredOption('-n, --name <name>', 'Name of the test sequence')
|
|
714
|
+
.requiredOption('-o, --output <dir>', 'Output directory for generated files')
|
|
715
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
716
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
717
|
+
.option('--app <applicationId>', 'Application ID (if name is not unique)')
|
|
718
|
+
.action(async (options) => {
|
|
719
|
+
try {
|
|
720
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
721
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
722
|
+
console.log(chalk_1.default.blue(`Fetching test sequence: ${options.name}`));
|
|
723
|
+
const exportData = await client.exportTestSequence(options.name, options.app);
|
|
724
|
+
const appType = exportData.application?.type || 'web';
|
|
725
|
+
if (appType === 'web') {
|
|
726
|
+
// Web apps → Playwright tests
|
|
727
|
+
console.log((0, PlaywrightGenerator_1.generateSummary)(exportData));
|
|
728
|
+
const generatorOptions = {
|
|
729
|
+
outputDir: options.output,
|
|
730
|
+
provider: 'local',
|
|
731
|
+
orchestration: 'websocket',
|
|
732
|
+
browsers: ['chromium'],
|
|
733
|
+
browser: 'chrome',
|
|
734
|
+
browserVersion: 'latest',
|
|
735
|
+
os: 'windows',
|
|
736
|
+
osVersion: '11',
|
|
737
|
+
apiUrl: baseUrl,
|
|
738
|
+
};
|
|
739
|
+
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
740
|
+
writeGeneratedFiles(files, options.output);
|
|
741
|
+
console.log(chalk_1.default.green('\nPlaywright tests exported successfully!'));
|
|
742
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
743
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
744
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
745
|
+
console.log(chalk_1.default.white(` npx playwright install`));
|
|
746
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
747
|
+
}
|
|
748
|
+
else if ((0, RestApiExecutor_1.isRestApiApp)(appType)) {
|
|
749
|
+
// REST API / SOAP → Axios tests
|
|
750
|
+
console.log((0, AxiosGenerator_1.generateAxiosSummary)(exportData));
|
|
751
|
+
const axiosOptions = {
|
|
752
|
+
outputDir: options.output,
|
|
753
|
+
};
|
|
754
|
+
const files = (0, AxiosGenerator_1.generateAxiosTests)(exportData, axiosOptions);
|
|
755
|
+
writeAxiosFiles(files, options.output);
|
|
756
|
+
console.log(chalk_1.default.green('\nAxios tests exported successfully!'));
|
|
757
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
758
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
759
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
760
|
+
console.log(chalk_1.default.white(` npx vitest run`));
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// Desktop or unsupported type
|
|
764
|
+
console.error(chalk_1.default.red(`\nError: Export is not supported for ${appType} applications.`));
|
|
765
|
+
console.log();
|
|
766
|
+
console.log(chalk_1.default.gray('Export currently supports:'));
|
|
767
|
+
console.log(chalk_1.default.white(' - Web applications (exports Playwright tests)'));
|
|
768
|
+
console.log(chalk_1.default.white(' - REST API applications (exports Axios tests)'));
|
|
769
|
+
console.log(chalk_1.default.white(' - SOAP services (exports Axios tests)'));
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
handleApiError(error);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
// ================== GENERIC STATUS COMMAND ==================
|
|
778
|
+
program
|
|
779
|
+
.command('status')
|
|
780
|
+
.description('Get status of any execution (auto-detects type from execution ID)')
|
|
781
|
+
.requiredOption('-e, --execution-id <id>', 'Execution ID')
|
|
782
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
783
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
784
|
+
.option('--json', 'Output as JSON')
|
|
785
|
+
.action(async (options) => {
|
|
786
|
+
try {
|
|
787
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
788
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
789
|
+
// Detect type from execution ID prefix
|
|
790
|
+
const isSequence = options.executionId.startsWith('seq_');
|
|
791
|
+
const isGenerated = options.executionId.startsWith('gen_');
|
|
792
|
+
if (isGenerated) {
|
|
793
|
+
// Handle generated test executions
|
|
794
|
+
const status = await client.getGenerateStatus(options.executionId);
|
|
795
|
+
if (options.json) {
|
|
796
|
+
console.log(JSON.stringify(status, null, 2));
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
console.log();
|
|
800
|
+
console.log(chalk_1.default.blue(`Generated Execution Status`));
|
|
801
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
802
|
+
console.log(` Execution ID: ${chalk_1.default.cyan(status.executionId)}`);
|
|
803
|
+
console.log(` Source: ${chalk_1.default.white(status.sourceName)} (${status.sourceType})`);
|
|
804
|
+
const statusColor = status.status === 'passed' ? chalk_1.default.green :
|
|
805
|
+
status.status === 'failed' ? chalk_1.default.red :
|
|
806
|
+
status.status === 'pending' ? chalk_1.default.gray :
|
|
807
|
+
status.status === 'running' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
808
|
+
console.log(` Status: ${statusColor(status.status.toUpperCase())}`);
|
|
809
|
+
console.log();
|
|
810
|
+
console.log(chalk_1.default.blue(`Summary`));
|
|
811
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
812
|
+
console.log(` Total: ${status.summary.total}`);
|
|
813
|
+
console.log(` Passed: ${chalk_1.default.green(status.summary.passed.toString())}`);
|
|
814
|
+
console.log(` Failed: ${chalk_1.default.red(status.summary.failed.toString())}`);
|
|
815
|
+
console.log(` Error: ${chalk_1.default.red(status.summary.error.toString())}`);
|
|
816
|
+
console.log(` Pending: ${chalk_1.default.yellow(status.summary.pending.toString())}`);
|
|
817
|
+
if (status.performance.totalDuration > 0) {
|
|
818
|
+
console.log();
|
|
819
|
+
console.log(chalk_1.default.blue(`Performance`));
|
|
820
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
821
|
+
console.log(` Total Duration: ${(status.performance.totalDuration / 1000).toFixed(1)}s`);
|
|
822
|
+
console.log(` Avg Test Duration: ${(status.performance.avgTestDuration / 1000).toFixed(1)}s`);
|
|
823
|
+
}
|
|
824
|
+
if (status.tests && status.tests.length > 0) {
|
|
825
|
+
console.log();
|
|
826
|
+
console.log(chalk_1.default.blue(`Test Results`));
|
|
827
|
+
console.log(chalk_1.default.gray(`─────────────────────────────────────`));
|
|
828
|
+
status.tests.forEach((test) => {
|
|
829
|
+
const icon = test.status === 'passed' ? chalk_1.default.green('✓') :
|
|
830
|
+
test.status === 'failed' ? chalk_1.default.red('✗') :
|
|
831
|
+
test.status === 'error' ? chalk_1.default.red('!') : chalk_1.default.gray('○');
|
|
832
|
+
const time = test.duration ? `(${(test.duration / 1000).toFixed(1)}s)` : '';
|
|
833
|
+
const browser = test.browser ? chalk_1.default.gray(` [${test.browser}]`) : '';
|
|
834
|
+
console.log(` ${icon} ${test.testName} ${chalk_1.default.gray(time)}${browser}`);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
console.log();
|
|
838
|
+
console.log(chalk_1.default.gray(`Last updated: ${status.lastUpdated}`));
|
|
839
|
+
}
|
|
840
|
+
process.exit(status.status === 'passed' ? 0 : status.status === 'running' || status.status === 'pending' ? 0 : 1);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
// Handle test set or sequence executions
|
|
844
|
+
const status = isSequence
|
|
845
|
+
? await client.getTestSequenceStatus(options.executionId)
|
|
846
|
+
: await client.getTestSetStatus(options.executionId);
|
|
847
|
+
if (options.json) {
|
|
848
|
+
console.log(JSON.stringify(status, null, 2));
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
displayStatus(status, isSequence ? 'sequence' : 'testset');
|
|
852
|
+
}
|
|
853
|
+
process.exit(status.status === 'passed' ? 0 : status.status === 'running' ? 0 : 1);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
handleApiError(error);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
// ================== HELP COMMAND ==================
|
|
861
|
+
program
|
|
862
|
+
.command('help [command]')
|
|
863
|
+
.description('Display help for a command')
|
|
864
|
+
.action((commandName) => {
|
|
865
|
+
if (commandName) {
|
|
866
|
+
const cmd = program.commands.find(c => c.name() === commandName || c.aliases().includes(commandName));
|
|
867
|
+
if (cmd) {
|
|
868
|
+
cmd.outputHelp();
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
console.error(chalk_1.default.red(`Unknown command: ${commandName}`));
|
|
872
|
+
console.log(chalk_1.default.gray('Run "qate --help" to see available commands'));
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
console.log(chalk_1.default.blue.bold('\n Qate CLI - Test Automation for CI/CD Pipelines\n'));
|
|
878
|
+
console.log(chalk_1.default.white(' Generate and run Playwright tests from your Qate test definitions.\n'));
|
|
879
|
+
console.log(chalk_1.default.yellow(' GETTING STARTED'));
|
|
880
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
881
|
+
console.log(' 1. Get your API key from: Settings > CI/CD API Keys');
|
|
882
|
+
console.log(' 2. Set the environment variable:');
|
|
883
|
+
console.log(chalk_1.default.cyan(' export QATE_API_KEY=qate_xxxxxxxxxxxx'));
|
|
884
|
+
console.log(' 3. Run your first command:');
|
|
885
|
+
console.log(chalk_1.default.cyan(' qate list\n'));
|
|
886
|
+
console.log(chalk_1.default.yellow(' TEST SET COMMANDS (Parallel Execution)'));
|
|
887
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
888
|
+
console.log(' qate list List all test sets');
|
|
889
|
+
console.log(' qate generate -n <name> -o <dir> Generate Playwright tests (web)');
|
|
890
|
+
console.log(' qate run -n <name> [--wait] Execute REST/SOAP tests locally');
|
|
891
|
+
console.log(' qate status:testset -e <id> Check test set execution status');
|
|
892
|
+
console.log(' qate export:testset -n <name> -o . Export as Playwright/Axios tests\n');
|
|
893
|
+
console.log(chalk_1.default.yellow(' TEST SEQUENCE COMMANDS (Sequential Execution)'));
|
|
894
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
895
|
+
console.log(' qate list:sequences List all test sequences');
|
|
896
|
+
console.log(' qate generate:sequence -n <name> Generate Playwright tests (web)');
|
|
897
|
+
console.log(' qate run:sequence -n <name> Execute REST/SOAP tests locally');
|
|
898
|
+
console.log(' qate status:sequence -e <id> Check sequence execution status');
|
|
899
|
+
console.log(' qate export:sequence -n <name> -o . Export as Playwright/Axios tests\n');
|
|
900
|
+
console.log(chalk_1.default.yellow(' UTILITY COMMANDS'));
|
|
901
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
902
|
+
console.log(' qate status -e <id> Auto-detect and show status');
|
|
903
|
+
console.log(' qate help [command] Show help for a command');
|
|
904
|
+
console.log(' qate --version Show CLI version\n');
|
|
905
|
+
console.log(chalk_1.default.yellow(' EXAMPLES'));
|
|
906
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
907
|
+
console.log(chalk_1.default.gray(' # Run tests and wait for completion'));
|
|
908
|
+
console.log(' qate run -n "Regression Suite" --wait\n');
|
|
909
|
+
console.log(chalk_1.default.gray(' # Generate Playwright tests for local execution'));
|
|
910
|
+
console.log(' qate generate -n "Regression Suite" -o ./e2e\n');
|
|
911
|
+
console.log(chalk_1.default.gray(' # Generate tests for BrowserStack'));
|
|
912
|
+
console.log(' qate generate -n "Regression Suite" -o ./e2e --provider browserstack\n');
|
|
913
|
+
console.log(chalk_1.default.gray(' # Generate tests for LambdaTest'));
|
|
914
|
+
console.log(' qate generate -n "Regression Suite" -o ./e2e --provider lambdatest\n');
|
|
915
|
+
console.log(chalk_1.default.gray(' # Export as standalone test code (auto-detects app type)'));
|
|
916
|
+
console.log(' qate export:testset -n "Regression Suite" -o ./exported-tests\n');
|
|
917
|
+
console.log(chalk_1.default.yellow(' CONFIGURATION'));
|
|
918
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
919
|
+
console.log(' The API key can be provided in two ways:');
|
|
920
|
+
console.log(' • Environment variable: ' + chalk_1.default.cyan('export QATE_API_KEY=qate_xxx'));
|
|
921
|
+
console.log(' • Command line option: ' + chalk_1.default.cyan('qate list --api-key qate_xxx') + '\n');
|
|
922
|
+
console.log(chalk_1.default.yellow(' ENVIRONMENT VARIABLES'));
|
|
923
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────────'));
|
|
924
|
+
console.log(' QATE_API_KEY ' + chalk_1.default.white('Your Qate API key (required)'));
|
|
925
|
+
console.log(' QATE_API_URL ' + chalk_1.default.gray('API URL (default: https://api.qate.ai)'));
|
|
926
|
+
console.log(' BROWSERSTACK_USERNAME ' + chalk_1.default.gray('BrowserStack username'));
|
|
927
|
+
console.log(' BROWSERSTACK_ACCESS_KEY ' + chalk_1.default.gray('BrowserStack access key'));
|
|
928
|
+
console.log(' SAUCE_USERNAME ' + chalk_1.default.gray('Sauce Labs username'));
|
|
929
|
+
console.log(' SAUCE_ACCESS_KEY ' + chalk_1.default.gray('Sauce Labs access key'));
|
|
930
|
+
console.log(' LT_USERNAME ' + chalk_1.default.gray('LambdaTest username'));
|
|
931
|
+
console.log(' LT_ACCESS_KEY ' + chalk_1.default.gray('LambdaTest access key') + '\n');
|
|
932
|
+
console.log(chalk_1.default.gray(' For more details on a command, run: qate help <command>'));
|
|
933
|
+
console.log(chalk_1.default.gray(' Documentation: https://docs.qate.io/cli\n'));
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
// ================== PLAYWRIGHT GENERATION COMMANDS ==================
|
|
937
|
+
/**
|
|
938
|
+
* Helper function to write generated files to output directory
|
|
939
|
+
*/
|
|
940
|
+
function writeGeneratedFiles(files, outputDir, silent = false) {
|
|
941
|
+
// Create output directory
|
|
942
|
+
if (!fs.existsSync(outputDir)) {
|
|
943
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
944
|
+
}
|
|
945
|
+
// Create tests subdirectory
|
|
946
|
+
const testsDir = path.join(outputDir, 'tests');
|
|
947
|
+
if (!fs.existsSync(testsDir)) {
|
|
948
|
+
fs.mkdirSync(testsDir, { recursive: true });
|
|
949
|
+
}
|
|
950
|
+
// Write config and package.json
|
|
951
|
+
fs.writeFileSync(path.join(outputDir, 'playwright.config.ts'), files['playwright.config.ts']);
|
|
952
|
+
fs.writeFileSync(path.join(outputDir, 'package.json'), files['package.json']);
|
|
953
|
+
// Write Qate reporter for result reporting
|
|
954
|
+
if (files['qate-reporter.ts']) {
|
|
955
|
+
fs.writeFileSync(path.join(outputDir, 'qate-reporter.ts'), files['qate-reporter.ts']);
|
|
956
|
+
}
|
|
957
|
+
// Write qate-runtime.ts (always needed for step timing functions used by reporter)
|
|
958
|
+
if (files['qate-runtime.ts']) {
|
|
959
|
+
fs.writeFileSync(path.join(outputDir, 'qate-runtime.ts'), files['qate-runtime.ts']);
|
|
960
|
+
}
|
|
961
|
+
// Write Sauce Labs config if present
|
|
962
|
+
if (files['.sauce/config.yml']) {
|
|
963
|
+
const sauceDir = path.join(outputDir, '.sauce');
|
|
964
|
+
if (!fs.existsSync(sauceDir)) {
|
|
965
|
+
fs.mkdirSync(sauceDir, { recursive: true });
|
|
966
|
+
}
|
|
967
|
+
fs.writeFileSync(path.join(sauceDir, 'config.yml'), files['.sauce/config.yml']);
|
|
968
|
+
}
|
|
969
|
+
// Write test files
|
|
970
|
+
for (const [filename, content] of Object.entries(files.tests)) {
|
|
971
|
+
fs.writeFileSync(path.join(testsDir, filename), content);
|
|
972
|
+
}
|
|
973
|
+
if (!silent) {
|
|
974
|
+
console.log(chalk_1.default.green(`\nGenerated files in ${outputDir}:`));
|
|
975
|
+
console.log(chalk_1.default.gray(` playwright.config.ts`));
|
|
976
|
+
console.log(chalk_1.default.gray(` package.json`));
|
|
977
|
+
console.log(chalk_1.default.gray(` qate-reporter.ts`));
|
|
978
|
+
console.log(chalk_1.default.gray(` qate-runtime.ts`));
|
|
979
|
+
if (files['.sauce/config.yml']) {
|
|
980
|
+
console.log(chalk_1.default.gray(` .sauce/config.yml`));
|
|
981
|
+
}
|
|
982
|
+
console.log(chalk_1.default.gray(` tests/`));
|
|
983
|
+
Object.keys(files.tests).forEach(filename => {
|
|
984
|
+
console.log(chalk_1.default.gray(` ${filename}`));
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Helper function to write generated Axios test files to output directory
|
|
990
|
+
*/
|
|
991
|
+
function writeAxiosFiles(files, outputDir, silent = false) {
|
|
992
|
+
// Create output directory
|
|
993
|
+
if (!fs.existsSync(outputDir)) {
|
|
994
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
995
|
+
}
|
|
996
|
+
// Create tests subdirectory
|
|
997
|
+
const testsDir = path.join(outputDir, 'tests');
|
|
998
|
+
if (!fs.existsSync(testsDir)) {
|
|
999
|
+
fs.mkdirSync(testsDir, { recursive: true });
|
|
1000
|
+
}
|
|
1001
|
+
// Write config and package.json
|
|
1002
|
+
fs.writeFileSync(path.join(outputDir, 'package.json'), files['package.json']);
|
|
1003
|
+
fs.writeFileSync(path.join(outputDir, 'vitest.config.ts'), files['vitest.config.ts']);
|
|
1004
|
+
// Write test files
|
|
1005
|
+
for (const [filename, content] of Object.entries(files.tests)) {
|
|
1006
|
+
fs.writeFileSync(path.join(testsDir, filename), content);
|
|
1007
|
+
}
|
|
1008
|
+
if (!silent) {
|
|
1009
|
+
console.log(chalk_1.default.green(`\nGenerated files in ${outputDir}:`));
|
|
1010
|
+
console.log(chalk_1.default.gray(` package.json`));
|
|
1011
|
+
console.log(chalk_1.default.gray(` vitest.config.ts`));
|
|
1012
|
+
console.log(chalk_1.default.gray(` tests/`));
|
|
1013
|
+
Object.keys(files.tests).forEach(filename => {
|
|
1014
|
+
console.log(chalk_1.default.gray(` ${filename}`));
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
program
|
|
1019
|
+
.command('generate:testset')
|
|
1020
|
+
.alias('generate')
|
|
1021
|
+
.description('Generate Playwright test files from a test set')
|
|
1022
|
+
.requiredOption('-n, --name <name>', 'Name of the test set')
|
|
1023
|
+
.requiredOption('-o, --output <dir>', 'Output directory for generated files')
|
|
1024
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
1025
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
1026
|
+
.option('--app <applicationId>', 'Application ID (if name is not unique)')
|
|
1027
|
+
.option('--provider <provider>', 'Test provider: local, browserstack, saucelabs, lambdatest', 'local')
|
|
1028
|
+
.option('--orchestration <mode>', 'Orchestration mode: websocket (run from CI) or cli (provider orchestrates)', 'websocket')
|
|
1029
|
+
.option('--browsers <browsers>', 'Comma-separated list of browsers (for local provider)', 'chromium')
|
|
1030
|
+
.option('--browser <browser>', 'Browser for cloud providers: chrome, firefox, safari, edge', 'chrome')
|
|
1031
|
+
.option('--browser-version <version>', 'Browser version for cloud providers', 'latest')
|
|
1032
|
+
.option('--os <os>', 'Operating system for cloud providers: windows, macos', 'windows')
|
|
1033
|
+
.option('--os-version <version>', 'OS version for cloud providers (e.g., 11, Sonoma)', '11')
|
|
1034
|
+
.option('--no-tracking', 'Disable result reporting to Qate')
|
|
1035
|
+
.action(async (options) => {
|
|
1036
|
+
try {
|
|
1037
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
1038
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
1039
|
+
console.log(chalk_1.default.blue(`Fetching test set: ${options.name}`));
|
|
1040
|
+
// Create execution record for tracking (unless disabled)
|
|
1041
|
+
let executionId;
|
|
1042
|
+
if (options.tracking !== false) {
|
|
1043
|
+
try {
|
|
1044
|
+
const generateResult = await client.createGenerateExecution(options.name, undefined, options.app, options.provider);
|
|
1045
|
+
executionId = generateResult.executionId;
|
|
1046
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
1047
|
+
}
|
|
1048
|
+
catch (err) {
|
|
1049
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record for tracking'));
|
|
1050
|
+
console.log(chalk_1.default.gray(' Results will not be reported to Qate'));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const exportData = await client.exportTestSet(options.name, options.app);
|
|
1054
|
+
// Show summary
|
|
1055
|
+
console.log((0, PlaywrightGenerator_1.generateSummary)(exportData));
|
|
1056
|
+
// Generate files with execution ID for result reporting
|
|
1057
|
+
const generatorOptions = {
|
|
1058
|
+
outputDir: options.output,
|
|
1059
|
+
provider: options.provider,
|
|
1060
|
+
orchestration: options.orchestration,
|
|
1061
|
+
browsers: options.browsers.split(',').map((b) => b.trim()),
|
|
1062
|
+
browser: options.browser,
|
|
1063
|
+
browserVersion: options.browserVersion,
|
|
1064
|
+
os: options.os,
|
|
1065
|
+
osVersion: options.osVersion,
|
|
1066
|
+
executionId,
|
|
1067
|
+
apiUrl: baseUrl,
|
|
1068
|
+
};
|
|
1069
|
+
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
1070
|
+
// Write files
|
|
1071
|
+
writeGeneratedFiles(files, options.output);
|
|
1072
|
+
console.log(chalk_1.default.green('\nPlaywright tests generated successfully!'));
|
|
1073
|
+
if (executionId) {
|
|
1074
|
+
console.log(chalk_1.default.cyan(`\nExecution tracking enabled`));
|
|
1075
|
+
console.log(chalk_1.default.gray(` Results will be reported to Qate when tests complete`));
|
|
1076
|
+
console.log(chalk_1.default.gray(` Check status: qate status -e ${executionId}`));
|
|
1077
|
+
}
|
|
1078
|
+
// Show platform targeting and orchestration info for cloud providers
|
|
1079
|
+
if (options.provider !== 'local') {
|
|
1080
|
+
const effectiveOrchestration = options.provider === 'saucelabs' ? 'cli' : options.orchestration;
|
|
1081
|
+
console.log(chalk_1.default.cyan(`\nPlatform targeting:`));
|
|
1082
|
+
console.log(chalk_1.default.gray(` Browser: ${options.browser} (${options.browserVersion})`));
|
|
1083
|
+
console.log(chalk_1.default.gray(` OS: ${options.os} ${options.osVersion}`));
|
|
1084
|
+
console.log(chalk_1.default.gray(` Orchestration: ${effectiveOrchestration}`));
|
|
1085
|
+
}
|
|
1086
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
1087
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
1088
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
1089
|
+
// Show provider-specific run commands based on orchestration mode
|
|
1090
|
+
const effectiveOrchestration = options.provider === 'saucelabs' ? 'cli' : options.orchestration;
|
|
1091
|
+
if (options.provider === 'saucelabs') {
|
|
1092
|
+
console.log(chalk_1.default.white(` npm install -g saucectl`));
|
|
1093
|
+
console.log(chalk_1.default.white(` saucectl run`));
|
|
1094
|
+
}
|
|
1095
|
+
else if (options.provider === 'browserstack' && effectiveOrchestration === 'cli') {
|
|
1096
|
+
console.log(chalk_1.default.white(` npm install -D browserstack-node-sdk`));
|
|
1097
|
+
console.log(chalk_1.default.white(` npx browserstack-node-sdk playwright test`));
|
|
1098
|
+
}
|
|
1099
|
+
else if (options.provider === 'lambdatest' && effectiveOrchestration === 'cli') {
|
|
1100
|
+
console.log(chalk_1.default.white(` # Download HyperExecute CLI from LambdaTest`));
|
|
1101
|
+
console.log(chalk_1.default.white(` ./hyperexecute --config hyperexecute.yaml`));
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
console.log(chalk_1.default.white(` npx playwright install`));
|
|
1105
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
handleApiError(error);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
program
|
|
1113
|
+
.command('generate:sequence')
|
|
1114
|
+
.description('Generate Playwright test files from a test sequence')
|
|
1115
|
+
.requiredOption('-n, --name <name>', 'Name of the test sequence')
|
|
1116
|
+
.requiredOption('-o, --output <dir>', 'Output directory for generated files')
|
|
1117
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
1118
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
1119
|
+
.option('--app <applicationId>', 'Application ID (if name is not unique)')
|
|
1120
|
+
.option('--provider <provider>', 'Test provider: local, browserstack, saucelabs, lambdatest', 'local')
|
|
1121
|
+
.option('--orchestration <mode>', 'Orchestration mode: websocket (run from CI) or cli (provider orchestrates)', 'websocket')
|
|
1122
|
+
.option('--browsers <browsers>', 'Comma-separated list of browsers (for local provider)', 'chromium')
|
|
1123
|
+
.option('--browser <browser>', 'Browser for cloud providers: chrome, firefox, safari, edge', 'chrome')
|
|
1124
|
+
.option('--browser-version <version>', 'Browser version for cloud providers', 'latest')
|
|
1125
|
+
.option('--os <os>', 'Operating system for cloud providers: windows, macos', 'windows')
|
|
1126
|
+
.option('--os-version <version>', 'OS version for cloud providers (e.g., 11, Sonoma)', '11')
|
|
1127
|
+
.option('--no-tracking', 'Disable result reporting to Qate')
|
|
1128
|
+
.action(async (options) => {
|
|
1129
|
+
try {
|
|
1130
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
1131
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
1132
|
+
console.log(chalk_1.default.blue(`Fetching test sequence: ${options.name}`));
|
|
1133
|
+
// Create execution record for tracking (unless disabled)
|
|
1134
|
+
let executionId;
|
|
1135
|
+
if (options.tracking !== false) {
|
|
1136
|
+
try {
|
|
1137
|
+
const generateResult = await client.createGenerateExecution(undefined, options.name, options.app, options.provider);
|
|
1138
|
+
executionId = generateResult.executionId;
|
|
1139
|
+
console.log(chalk_1.default.gray(`Execution ID: ${executionId}`));
|
|
1140
|
+
}
|
|
1141
|
+
catch (err) {
|
|
1142
|
+
console.log(chalk_1.default.yellow('Note: Could not create execution record for tracking'));
|
|
1143
|
+
console.log(chalk_1.default.gray(' Results will not be reported to Qate'));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const exportData = await client.exportTestSequence(options.name, options.app);
|
|
1147
|
+
// Show summary
|
|
1148
|
+
console.log((0, PlaywrightGenerator_1.generateSummary)(exportData));
|
|
1149
|
+
// Generate files with execution ID for result reporting
|
|
1150
|
+
const generatorOptions = {
|
|
1151
|
+
outputDir: options.output,
|
|
1152
|
+
provider: options.provider,
|
|
1153
|
+
orchestration: options.orchestration,
|
|
1154
|
+
browsers: options.browsers.split(',').map((b) => b.trim()),
|
|
1155
|
+
browser: options.browser,
|
|
1156
|
+
browserVersion: options.browserVersion,
|
|
1157
|
+
os: options.os,
|
|
1158
|
+
osVersion: options.osVersion,
|
|
1159
|
+
executionId,
|
|
1160
|
+
apiUrl: baseUrl,
|
|
1161
|
+
};
|
|
1162
|
+
const files = (0, PlaywrightGenerator_1.generatePlaywrightTests)(exportData, generatorOptions);
|
|
1163
|
+
// Write files
|
|
1164
|
+
writeGeneratedFiles(files, options.output);
|
|
1165
|
+
console.log(chalk_1.default.green('\nPlaywright tests generated successfully!'));
|
|
1166
|
+
if (executionId) {
|
|
1167
|
+
console.log(chalk_1.default.cyan(`\nExecution tracking enabled`));
|
|
1168
|
+
console.log(chalk_1.default.gray(` Results will be reported to Qate when tests complete`));
|
|
1169
|
+
console.log(chalk_1.default.gray(` Check status: qate status -e ${executionId}`));
|
|
1170
|
+
}
|
|
1171
|
+
// Show platform targeting and orchestration info for cloud providers
|
|
1172
|
+
if (options.provider !== 'local') {
|
|
1173
|
+
const effectiveOrchestration = options.provider === 'saucelabs' ? 'cli' : options.orchestration;
|
|
1174
|
+
console.log(chalk_1.default.cyan(`\nPlatform targeting:`));
|
|
1175
|
+
console.log(chalk_1.default.gray(` Browser: ${options.browser} (${options.browserVersion})`));
|
|
1176
|
+
console.log(chalk_1.default.gray(` OS: ${options.os} ${options.osVersion}`));
|
|
1177
|
+
console.log(chalk_1.default.gray(` Orchestration: ${effectiveOrchestration}`));
|
|
1178
|
+
}
|
|
1179
|
+
console.log(chalk_1.default.gray('\nNext steps:'));
|
|
1180
|
+
console.log(chalk_1.default.white(` cd ${options.output}`));
|
|
1181
|
+
console.log(chalk_1.default.white(` npm install`));
|
|
1182
|
+
// Show provider-specific run commands based on orchestration mode
|
|
1183
|
+
const effectiveOrchestration = options.provider === 'saucelabs' ? 'cli' : options.orchestration;
|
|
1184
|
+
if (options.provider === 'saucelabs') {
|
|
1185
|
+
console.log(chalk_1.default.white(` npm install -g saucectl`));
|
|
1186
|
+
console.log(chalk_1.default.white(` saucectl run`));
|
|
1187
|
+
}
|
|
1188
|
+
else if (options.provider === 'browserstack' && effectiveOrchestration === 'cli') {
|
|
1189
|
+
console.log(chalk_1.default.white(` npm install -D browserstack-node-sdk`));
|
|
1190
|
+
console.log(chalk_1.default.white(` npx browserstack-node-sdk playwright test`));
|
|
1191
|
+
}
|
|
1192
|
+
else if (options.provider === 'lambdatest' && effectiveOrchestration === 'cli') {
|
|
1193
|
+
console.log(chalk_1.default.white(` # Download HyperExecute CLI from LambdaTest`));
|
|
1194
|
+
console.log(chalk_1.default.white(` ./hyperexecute --config hyperexecute.yaml`));
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
console.log(chalk_1.default.white(` npx playwright install`));
|
|
1198
|
+
console.log(chalk_1.default.white(` npx playwright test`));
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
catch (error) {
|
|
1202
|
+
handleApiError(error);
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
// ================== INSTALL COMMAND ==================
|
|
1206
|
+
program
|
|
1207
|
+
.command('install')
|
|
1208
|
+
.description('Install a build on a connected desktop agent')
|
|
1209
|
+
.requiredOption('--app <applicationId>', 'Application ID')
|
|
1210
|
+
.requiredOption('--path <installerPath>', 'Path to installer on the agent machine')
|
|
1211
|
+
.option('--args <arguments>', 'Installer arguments')
|
|
1212
|
+
.option('-a, --api-key <key>', 'API key (or use QATE_API_KEY env var)')
|
|
1213
|
+
.option('-u, --url <url>', 'Qate API URL (or use QATE_API_URL env var)')
|
|
1214
|
+
.option('--timeout <seconds>', 'Timeout in seconds (default: 300)', '300')
|
|
1215
|
+
.option('--json', 'Output as JSON')
|
|
1216
|
+
.action(async (options) => {
|
|
1217
|
+
try {
|
|
1218
|
+
const { apiKey, baseUrl } = getConfig(options);
|
|
1219
|
+
const client = new client_1.QateClient(baseUrl, apiKey);
|
|
1220
|
+
if (!options.json) {
|
|
1221
|
+
console.log(chalk_1.default.blue('Sending install request to desktop agent...'));
|
|
1222
|
+
console.log(chalk_1.default.gray(`Application: ${options.app}`));
|
|
1223
|
+
console.log(chalk_1.default.gray(`Installer: ${options.path}`));
|
|
1224
|
+
if (options.args) {
|
|
1225
|
+
console.log(chalk_1.default.gray(`Arguments: ${options.args}`));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const installResult = await client.installApplication(options.app, options.path, options.args);
|
|
1229
|
+
if (!options.json) {
|
|
1230
|
+
console.log(chalk_1.default.gray(`Request ID: ${installResult.requestId}`));
|
|
1231
|
+
console.log(chalk_1.default.blue('\nWaiting for install to complete...\n'));
|
|
1232
|
+
}
|
|
1233
|
+
const timeoutMs = parseInt(options.timeout) * 1000;
|
|
1234
|
+
const status = await client.pollInstall(installResult.requestId, timeoutMs);
|
|
1235
|
+
if (options.json) {
|
|
1236
|
+
console.log(JSON.stringify(status, null, 2));
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
if (status.status === 'completed') {
|
|
1240
|
+
console.log(chalk_1.default.green('Install completed successfully'));
|
|
1241
|
+
if (status.result) {
|
|
1242
|
+
console.log(chalk_1.default.gray(typeof status.result === 'string' ? status.result : JSON.stringify(status.result)));
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else if (status.status === 'failed') {
|
|
1246
|
+
console.log(chalk_1.default.red('Install failed'));
|
|
1247
|
+
if (status.error) {
|
|
1248
|
+
console.log(chalk_1.default.red(`Error: ${status.error}`));
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
console.log(chalk_1.default.yellow(`Install status: ${status.status}`));
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
process.exit(status.status === 'completed' ? 0 : 1);
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
handleApiError(error);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
// Show help if no command provided
|
|
1262
|
+
if (process.argv.length <= 2) {
|
|
1263
|
+
// Trigger the help command
|
|
1264
|
+
program.parse(['node', 'qate', 'help']);
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
program.parse();
|
|
1268
|
+
}
|
|
1269
|
+
//# sourceMappingURL=cli.js.map
|