wdio-lambdatest-service-sdk 5.0.0 → 5.1.1
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 +339 -36
- package/bin/cli.js +86 -0
- package/bin/generate-config.js +14 -166
- package/bin/setup.js +14 -128
- package/bin/wdio-lt.js +39 -0
- package/bun.lock +875 -0
- package/index.js +4 -5
- package/lib/cli/generate.js +526 -0
- package/lib/cli/setup.js +174 -0
- package/lib/cli/style.js +57 -0
- package/package.json +24 -4
- package/src/launcher.js +52 -29
- package/src/service.js +36 -40
package/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WDIO LambdaTest service entry point.
|
|
3
|
+
* Exposes the service class and launcher for WebdriverIO.
|
|
4
|
+
*/
|
|
1
5
|
const LambdaTestService = require('./src/service');
|
|
2
6
|
const LambdaTestLauncher = require('./src/launcher');
|
|
3
7
|
|
|
4
|
-
// Export the Service class as the main module export
|
|
5
8
|
module.exports = LambdaTestService;
|
|
6
|
-
|
|
7
|
-
// Also export as 'default' for compatibility with some WDIO loaders
|
|
8
9
|
module.exports.default = LambdaTestService;
|
|
9
|
-
|
|
10
|
-
// Export the Launcher
|
|
11
10
|
module.exports.launcher = LambdaTestLauncher;
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate command logic: Interactively create a WDIO config for LambdaTest.
|
|
3
|
+
*/
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
|
|
9
|
+
/** Safe filename: non-empty, no path separators or reserved chars. */
|
|
10
|
+
function toSafeFilename(value, fallback) {
|
|
11
|
+
if (!value || typeof value !== 'string') return fallback;
|
|
12
|
+
const sanitized = value.replace(/[/\\?*:|<>"]/g, '').trim();
|
|
13
|
+
return sanitized || fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find wdio binary path relative to outputDir. Checks root node_modules then android-sample.
|
|
18
|
+
* @param {string} outDir - Absolute path to LT_Test (output dir)
|
|
19
|
+
* @returns {string} Relative path to wdio binary
|
|
20
|
+
*/
|
|
21
|
+
function findWdioBinRelative(outDir) {
|
|
22
|
+
const rootWdio = path.resolve(outDir, '../node_modules/.bin/wdio');
|
|
23
|
+
const androidSampleWdio = path.resolve(outDir, '../android-sample/node_modules/.bin/wdio');
|
|
24
|
+
if (fs.existsSync(rootWdio)) return '../node_modules/.bin/wdio';
|
|
25
|
+
if (fs.existsSync(androidSampleWdio)) return '../android-sample/node_modules/.bin/wdio';
|
|
26
|
+
return '../node_modules/.bin/wdio';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Device configuration defaults for LambdaTest.
|
|
31
|
+
*/
|
|
32
|
+
const DEVICE_DEFAULTS = {
|
|
33
|
+
// Virtual Device defaults
|
|
34
|
+
virtual: {
|
|
35
|
+
android: {
|
|
36
|
+
deviceName: 'Galaxy S24 Ultra',
|
|
37
|
+
platformVersion: '14',
|
|
38
|
+
appiumVersion: '2.16.2'
|
|
39
|
+
},
|
|
40
|
+
ios: {
|
|
41
|
+
deviceName: 'iPad (10th generation)',
|
|
42
|
+
platformVersion: '16',
|
|
43
|
+
appiumVersion: '2.1.3'
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
// Real Device defaults
|
|
47
|
+
real: {
|
|
48
|
+
android: {
|
|
49
|
+
deviceName: 'Pixel 8',
|
|
50
|
+
platformVersion: '14'
|
|
51
|
+
},
|
|
52
|
+
ios: {
|
|
53
|
+
deviceName: 'iPhone 15',
|
|
54
|
+
platformVersion: '17'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get default App ID based on platform.
|
|
61
|
+
*/
|
|
62
|
+
function getDefaultAppId(platform) {
|
|
63
|
+
const envAppId = process.env.LT_APP_ID;
|
|
64
|
+
if (envAppId) return envAppId;
|
|
65
|
+
return platform === 'ios' ? 'lt://proverbial-ios' : 'lt://proverbial-android';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build a single capability object.
|
|
70
|
+
*/
|
|
71
|
+
function buildSingleCapability(deviceType, platform, testingType, appId) {
|
|
72
|
+
const isReal = deviceType === 'real';
|
|
73
|
+
const platformName = platform === 'ios' ? 'ios' : 'android';
|
|
74
|
+
const defaults = DEVICE_DEFAULTS[deviceType][platform];
|
|
75
|
+
|
|
76
|
+
if (testingType === 'app') {
|
|
77
|
+
// Native Application Testing
|
|
78
|
+
if (isReal) {
|
|
79
|
+
return {
|
|
80
|
+
'lt:options': {
|
|
81
|
+
platformName: platformName,
|
|
82
|
+
deviceName: defaults.deviceName,
|
|
83
|
+
platformVersion: defaults.platformVersion,
|
|
84
|
+
app: appId,
|
|
85
|
+
isRealMobile: true
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
} else {
|
|
89
|
+
return {
|
|
90
|
+
'lt:options': {
|
|
91
|
+
platformName: platformName,
|
|
92
|
+
deviceName: defaults.deviceName,
|
|
93
|
+
appiumVersion: defaults.appiumVersion,
|
|
94
|
+
platformVersion: defaults.platformVersion,
|
|
95
|
+
app: appId,
|
|
96
|
+
isRealMobile: false
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Website (Mobile Browser) Testing
|
|
102
|
+
const browserName = platform === 'ios' ? 'Safari' : 'Chrome';
|
|
103
|
+
if (isReal) {
|
|
104
|
+
return {
|
|
105
|
+
browserName: browserName,
|
|
106
|
+
'lt:options': {
|
|
107
|
+
platformName: platformName,
|
|
108
|
+
deviceName: defaults.deviceName,
|
|
109
|
+
platformVersion: defaults.platformVersion,
|
|
110
|
+
isRealMobile: true
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
return {
|
|
115
|
+
browserName: browserName,
|
|
116
|
+
'lt:options': {
|
|
117
|
+
platformName: platformName,
|
|
118
|
+
deviceName: defaults.deviceName,
|
|
119
|
+
appiumVersion: defaults.appiumVersion,
|
|
120
|
+
platformVersion: defaults.platformVersion,
|
|
121
|
+
isRealMobile: false
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build capabilities array from multi-select config.
|
|
130
|
+
* Generates all combinations of selected options.
|
|
131
|
+
*/
|
|
132
|
+
function buildCapabilitiesMulti(config) {
|
|
133
|
+
const capabilities = [];
|
|
134
|
+
|
|
135
|
+
// App Testing combinations
|
|
136
|
+
if (config.testTypes.includes('app')) {
|
|
137
|
+
for (const deviceType of config.deviceTypes) {
|
|
138
|
+
for (const platform of config.platforms) {
|
|
139
|
+
for (const testingType of config.testingTypes) {
|
|
140
|
+
const appId = testingType === 'app' ? (config.appIds[platform] || getDefaultAppId(platform)) : null;
|
|
141
|
+
capabilities.push(buildSingleCapability(deviceType, platform, testingType, appId));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Browser Testing (Desktop Website)
|
|
148
|
+
if (config.testTypes.includes('browser')) {
|
|
149
|
+
capabilities.push({
|
|
150
|
+
browserName: 'Chrome',
|
|
151
|
+
browserVersion: 'latest',
|
|
152
|
+
'LT:Options': {
|
|
153
|
+
username: config.username,
|
|
154
|
+
accessKey: config.key,
|
|
155
|
+
platformName: 'Windows 10',
|
|
156
|
+
project: config.testName,
|
|
157
|
+
w3c: true,
|
|
158
|
+
plugin: 'node_js-webdriverio'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return capabilities;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build config file template string.
|
|
168
|
+
*/
|
|
169
|
+
function buildConfigTemplate(config, commonCaps, capabilities) {
|
|
170
|
+
return `const path = require('path');
|
|
171
|
+
|
|
172
|
+
exports.config = {
|
|
173
|
+
// Authentication handled by SDK
|
|
174
|
+
// user: "${config.username}",
|
|
175
|
+
// key: "${config.key}",
|
|
176
|
+
|
|
177
|
+
updateJob: false,
|
|
178
|
+
specs: ["${config.specPath}"],
|
|
179
|
+
exclude: [],
|
|
180
|
+
|
|
181
|
+
maxInstances: ${config.parallel > 0 ? config.parallel : 1},
|
|
182
|
+
|
|
183
|
+
commonCapabilities: ${JSON.stringify(commonCaps, null, 4)},
|
|
184
|
+
|
|
185
|
+
capabilities: ${JSON.stringify(capabilities, null, 4)},
|
|
186
|
+
|
|
187
|
+
logLevel: 'error',
|
|
188
|
+
coloredLogs: true,
|
|
189
|
+
screenshotPath: "./errorShots/",
|
|
190
|
+
baseUrl: "https://mobile-hub.lambdatest.com",
|
|
191
|
+
waitforTimeout: 10000,
|
|
192
|
+
connectionRetryTimeout: 90000,
|
|
193
|
+
connectionRetryCount: 3,
|
|
194
|
+
path: "/wd/hub",
|
|
195
|
+
hostname: "mobile-hub.lambdatest.com",
|
|
196
|
+
port: 80,
|
|
197
|
+
|
|
198
|
+
services: [
|
|
199
|
+
[path.join(__dirname, '../wdio-lambdatest-service'), {
|
|
200
|
+
user: "${config.username}",
|
|
201
|
+
key: "${config.key}"
|
|
202
|
+
}]
|
|
203
|
+
],
|
|
204
|
+
|
|
205
|
+
framework: "mocha",
|
|
206
|
+
mochaOpts: {
|
|
207
|
+
ui: "bdd",
|
|
208
|
+
timeout: 20000,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Merge commonCapabilities into each capability
|
|
213
|
+
exports.config.capabilities.forEach(function (caps) {
|
|
214
|
+
for (const i in exports.config.commonCapabilities) {
|
|
215
|
+
caps[i] = caps[i] || exports.config.commonCapabilities[i];
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function printBanner() {
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(chalk.cyan.bold('╭────────────────────────────────────────────╮'));
|
|
224
|
+
console.log(chalk.cyan.bold('│') + ' 🛠️ ' + chalk.bold('LambdaTest WDIO Config Generator') + ' ' + chalk.cyan.bold('│'));
|
|
225
|
+
console.log(chalk.cyan.bold('╰────────────────────────────────────────────╯'));
|
|
226
|
+
console.log();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Run the generate command interactively.
|
|
231
|
+
* @returns {Promise<void>}
|
|
232
|
+
*/
|
|
233
|
+
async function runGenerate() {
|
|
234
|
+
const outputDir = path.resolve(process.cwd(), 'LT_Test');
|
|
235
|
+
|
|
236
|
+
if (!fs.existsSync(outputDir)) {
|
|
237
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
printBanner();
|
|
241
|
+
|
|
242
|
+
const defaultUsername = process.env.LT_USERNAME || '';
|
|
243
|
+
const defaultKey = process.env.LT_ACCESS_KEY || '';
|
|
244
|
+
|
|
245
|
+
// Basic Info
|
|
246
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
247
|
+
console.log(chalk.cyan.bold(' 📋 Basic Info'));
|
|
248
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
249
|
+
console.log();
|
|
250
|
+
|
|
251
|
+
const basicAnswers = await inquirer.default.prompt([
|
|
252
|
+
{
|
|
253
|
+
type: 'input',
|
|
254
|
+
name: 'testName',
|
|
255
|
+
message: 'Test name:',
|
|
256
|
+
default: 'LambdaTest',
|
|
257
|
+
validate: (input) => input.trim() !== '' || 'Test name is required'
|
|
258
|
+
}
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
const config = {
|
|
262
|
+
testName: toSafeFilename(basicAnswers.testName, 'LambdaTest'),
|
|
263
|
+
filename: '',
|
|
264
|
+
username: '',
|
|
265
|
+
key: '',
|
|
266
|
+
testTypes: [], // ['app', 'browser']
|
|
267
|
+
deviceTypes: [], // ['real', 'virtual']
|
|
268
|
+
platforms: [], // ['android', 'ios']
|
|
269
|
+
testingTypes: [], // ['app', 'website']
|
|
270
|
+
appIds: {}, // { android: 'lt://...', ios: 'lt://...' }
|
|
271
|
+
parallel: 0,
|
|
272
|
+
specPath: ''
|
|
273
|
+
};
|
|
274
|
+
config.filename = `${config.testName}.conf.js`;
|
|
275
|
+
|
|
276
|
+
// Credentials
|
|
277
|
+
console.log();
|
|
278
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
279
|
+
console.log(chalk.cyan.bold(' 🔐 LambdaTest Credentials'));
|
|
280
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
281
|
+
console.log();
|
|
282
|
+
|
|
283
|
+
const credAnswers = await inquirer.default.prompt([
|
|
284
|
+
{
|
|
285
|
+
type: 'input',
|
|
286
|
+
name: 'username',
|
|
287
|
+
message: 'LambdaTest Username:',
|
|
288
|
+
default: defaultUsername || undefined,
|
|
289
|
+
validate: (input) => input.trim() !== '' || 'Username is required'
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
type: 'password',
|
|
293
|
+
name: 'key',
|
|
294
|
+
message: 'LambdaTest Access Key:',
|
|
295
|
+
default: defaultKey || undefined,
|
|
296
|
+
mask: '*',
|
|
297
|
+
validate: (input) => input.trim() !== '' || 'Access key is required'
|
|
298
|
+
}
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
config.username = credAnswers.username;
|
|
302
|
+
config.key = credAnswers.key;
|
|
303
|
+
|
|
304
|
+
// Test Configuration
|
|
305
|
+
console.log();
|
|
306
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
307
|
+
console.log(chalk.cyan.bold(' ⚙️ Test Configuration'));
|
|
308
|
+
console.log(chalk.dim('─' .repeat(40)));
|
|
309
|
+
console.log(chalk.dim(' (Use space to select, enter to confirm)'));
|
|
310
|
+
console.log();
|
|
311
|
+
|
|
312
|
+
// Step 1: Test Type (Multi-select)
|
|
313
|
+
const testTypeAnswer = await inquirer.default.prompt([
|
|
314
|
+
{
|
|
315
|
+
type: 'checkbox',
|
|
316
|
+
name: 'testTypes',
|
|
317
|
+
message: 'Test type(s):',
|
|
318
|
+
choices: [
|
|
319
|
+
{ name: '📱 Mobile Testing ', value: 'app', checked: true },
|
|
320
|
+
{ name: '🌐 Browser Testing (Desktop)', value: 'browser' }
|
|
321
|
+
],
|
|
322
|
+
validate: (input) => input.length > 0 || 'Please select at least one test type'
|
|
323
|
+
}
|
|
324
|
+
]);
|
|
325
|
+
config.testTypes = testTypeAnswer.testTypes;
|
|
326
|
+
|
|
327
|
+
// If App Testing is selected, ask for more details
|
|
328
|
+
if (config.testTypes.includes('app')) {
|
|
329
|
+
console.log();
|
|
330
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
331
|
+
console.log(chalk.cyan.bold(' 📱 App Testing Configuration'));
|
|
332
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
333
|
+
console.log();
|
|
334
|
+
|
|
335
|
+
// Step 2: Device Type (Multi-select)
|
|
336
|
+
const deviceAnswer = await inquirer.default.prompt([
|
|
337
|
+
{
|
|
338
|
+
type: 'checkbox',
|
|
339
|
+
name: 'deviceTypes',
|
|
340
|
+
message: 'Device type(s):',
|
|
341
|
+
choices: [
|
|
342
|
+
{ name: '📲 Mobile Device', value: 'real', checked: true },
|
|
343
|
+
{ name: '💻 Website (Desktop)', value: 'virtual' }
|
|
344
|
+
],
|
|
345
|
+
validate: (input) => input.length > 0 || 'Please select at least one device type'
|
|
346
|
+
}
|
|
347
|
+
]);
|
|
348
|
+
config.deviceTypes = deviceAnswer.deviceTypes;
|
|
349
|
+
|
|
350
|
+
// Step 3: Platform (Multi-select)
|
|
351
|
+
const platformAnswer = await inquirer.default.prompt([
|
|
352
|
+
{
|
|
353
|
+
type: 'checkbox',
|
|
354
|
+
name: 'platforms',
|
|
355
|
+
message: 'Platform(s):',
|
|
356
|
+
choices: [
|
|
357
|
+
{ name: '🤖 Android', value: 'android', checked: true },
|
|
358
|
+
{ name: '🍎 iOS', value: 'ios' }
|
|
359
|
+
],
|
|
360
|
+
validate: (input) => input.length > 0 || 'Please select at least one platform'
|
|
361
|
+
}
|
|
362
|
+
]);
|
|
363
|
+
config.platforms = platformAnswer.platforms;
|
|
364
|
+
|
|
365
|
+
// Step 4: What to test (Multi-select)
|
|
366
|
+
const testingTypeAnswer = await inquirer.default.prompt([
|
|
367
|
+
{
|
|
368
|
+
type: 'checkbox',
|
|
369
|
+
name: 'testingTypes',
|
|
370
|
+
message: 'What do you want to test?',
|
|
371
|
+
choices: [
|
|
372
|
+
{ name: '📦 Application (Native App)', value: 'app', checked: true },
|
|
373
|
+
{ name: '🌐 Website (Mobile Browser)', value: 'website' }
|
|
374
|
+
],
|
|
375
|
+
validate: (input) => input.length > 0 || 'Please select at least one testing type'
|
|
376
|
+
}
|
|
377
|
+
]);
|
|
378
|
+
config.testingTypes = testingTypeAnswer.testingTypes;
|
|
379
|
+
|
|
380
|
+
// Step 5: App IDs (only if testing native applications)
|
|
381
|
+
if (config.testingTypes.includes('app')) {
|
|
382
|
+
console.log();
|
|
383
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
384
|
+
console.log(chalk.cyan.bold(' 📦 App Configuration'));
|
|
385
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
386
|
+
console.log();
|
|
387
|
+
|
|
388
|
+
// Ask for App ID for each selected platform
|
|
389
|
+
for (const platform of config.platforms) {
|
|
390
|
+
const platformLabel = platform === 'ios' ? 'iOS' : 'Android';
|
|
391
|
+
const defaultAppId = getDefaultAppId(platform);
|
|
392
|
+
|
|
393
|
+
const appIdAnswer = await inquirer.default.prompt([
|
|
394
|
+
{
|
|
395
|
+
type: 'input',
|
|
396
|
+
name: 'appId',
|
|
397
|
+
message: `${platformLabel} App ID (e.g. lt://APP123456789):`,
|
|
398
|
+
default: defaultAppId,
|
|
399
|
+
validate: (input) => input.trim() !== '' || 'App ID is required'
|
|
400
|
+
}
|
|
401
|
+
]);
|
|
402
|
+
config.appIds[platform] = appIdAnswer.appId.trim();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Parallel threads
|
|
408
|
+
console.log();
|
|
409
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
410
|
+
console.log(chalk.cyan.bold(' 🚀 Execution Settings'));
|
|
411
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
412
|
+
console.log();
|
|
413
|
+
|
|
414
|
+
const parallelAnswer = await inquirer.default.prompt([
|
|
415
|
+
{
|
|
416
|
+
type: 'number',
|
|
417
|
+
name: 'parallel',
|
|
418
|
+
message: 'Parallel threads (0 for sequential):',
|
|
419
|
+
default: 0,
|
|
420
|
+
validate: (input) => {
|
|
421
|
+
const num = parseInt(input, 10);
|
|
422
|
+
return (!isNaN(num) && num >= 0) || 'Please enter a valid number (0 or greater)';
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
]);
|
|
426
|
+
config.parallel = parallelAnswer.parallel || 0;
|
|
427
|
+
|
|
428
|
+
// Spec file path
|
|
429
|
+
const specAnswer = await inquirer.default.prompt([
|
|
430
|
+
{
|
|
431
|
+
type: 'input',
|
|
432
|
+
name: 'specPath',
|
|
433
|
+
message: 'Spec file path:',
|
|
434
|
+
default: './specs/test.js'
|
|
435
|
+
}
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
let rawSpec = specAnswer.specPath.trim() || './specs/test.js';
|
|
439
|
+
|
|
440
|
+
// Smart resolve spec path relative to LT_Test
|
|
441
|
+
if (fs.existsSync(path.resolve(outputDir, rawSpec))) {
|
|
442
|
+
config.specPath = rawSpec;
|
|
443
|
+
} else {
|
|
444
|
+
const rootPath = path.resolve(outputDir, '..');
|
|
445
|
+
const relativeToRoot = rawSpec.replace(/^\.\//, '');
|
|
446
|
+
const pathFromRoot = path.join(rootPath, relativeToRoot);
|
|
447
|
+
|
|
448
|
+
if (fs.existsSync(pathFromRoot)) {
|
|
449
|
+
config.specPath = path.relative(outputDir, pathFromRoot).replace(/\\/g, '/');
|
|
450
|
+
} else {
|
|
451
|
+
if (!path.isAbsolute(rawSpec) && !rawSpec.startsWith('../')) {
|
|
452
|
+
config.specPath = '../' + relativeToRoot;
|
|
453
|
+
} else {
|
|
454
|
+
config.specPath = rawSpec;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const commonCaps = {
|
|
460
|
+
build: `LT_WDIO_${config.testName}_${new Date().toISOString().split('T')[0]}`,
|
|
461
|
+
name: config.testName,
|
|
462
|
+
visual: true,
|
|
463
|
+
console: true
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const capabilities = buildCapabilitiesMulti(config);
|
|
467
|
+
const template = buildConfigTemplate(config, commonCaps, capabilities);
|
|
468
|
+
|
|
469
|
+
const outputPath = path.join(outputDir, config.filename);
|
|
470
|
+
fs.writeFileSync(outputPath, template);
|
|
471
|
+
|
|
472
|
+
const packageJsonPath = path.join(outputDir, 'package.json');
|
|
473
|
+
let pkg = { name: 'lt-generated-tests', version: '1.0.0', scripts: {} };
|
|
474
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
475
|
+
try {
|
|
476
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
477
|
+
} catch (e) {
|
|
478
|
+
// Ignore parse errors, use default
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (!pkg.scripts || typeof pkg.scripts !== 'object') {
|
|
482
|
+
pkg.scripts = {};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const wdioBin = findWdioBinRelative(outputDir);
|
|
486
|
+
pkg.scripts[`test:${config.testName}`] = `${wdioBin} ${config.filename}`;
|
|
487
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
|
|
488
|
+
|
|
489
|
+
// Success output
|
|
490
|
+
console.log();
|
|
491
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
492
|
+
console.log(chalk.green.bold(' ✔ Configuration Created Successfully!'));
|
|
493
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
494
|
+
console.log();
|
|
495
|
+
|
|
496
|
+
// Show summary of selected options
|
|
497
|
+
console.log(chalk.cyan(' Summary:'));
|
|
498
|
+
console.log(chalk.dim(' ─────────────────────────'));
|
|
499
|
+
console.log(` ${chalk.bold('Test Types:')} ${config.testTypes.map(t => t === 'app' ? 'App Testing' : 'Browser Testing').join(', ')}`);
|
|
500
|
+
|
|
501
|
+
if (config.testTypes.includes('app')) {
|
|
502
|
+
console.log(` ${chalk.bold('Device Types:')} ${config.deviceTypes.map(d => d === 'real' ? 'Real Device' : 'Virtual Device').join(', ')}`);
|
|
503
|
+
console.log(` ${chalk.bold('Platforms:')} ${config.platforms.map(p => p === 'ios' ? 'iOS' : 'Android').join(', ')}`);
|
|
504
|
+
console.log(` ${chalk.bold('Testing:')} ${config.testingTypes.map(t => t === 'app' ? 'Native App' : 'Website').join(', ')}`);
|
|
505
|
+
|
|
506
|
+
if (config.testingTypes.includes('app')) {
|
|
507
|
+
for (const platform of config.platforms) {
|
|
508
|
+
const platformLabel = platform === 'ios' ? 'iOS' : 'Android';
|
|
509
|
+
console.log(` ${chalk.bold(`${platformLabel} App ID:`)} ${chalk.dim(config.appIds[platform])}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log(` ${chalk.bold('Capabilities:')} ${chalk.yellow(capabilities.length)} configuration(s) generated`);
|
|
515
|
+
console.log();
|
|
516
|
+
|
|
517
|
+
console.log(chalk.cyan(' Config file: ') + chalk.dim(outputPath));
|
|
518
|
+
console.log(chalk.cyan(' Package.json: ') + chalk.dim(packageJsonPath));
|
|
519
|
+
console.log();
|
|
520
|
+
console.log(chalk.bold(' To run your test:'));
|
|
521
|
+
console.log();
|
|
522
|
+
console.log(chalk.green(` cd LT_Test && npm run test:${config.testName}`));
|
|
523
|
+
console.log();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = { runGenerate };
|