wdio-lambdatest-service-sdk 5.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 +80 -0
- package/bin/generate-config.js +167 -0
- package/bin/setup.js +129 -0
- package/index.js +11 -0
- package/package.json +29 -0
- package/src/launcher.js +45 -0
- package/src/service.js +97 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# WDIO LambdaTest Service
|
|
2
|
+
|
|
3
|
+
This WebdriverIO service provides seamless integration with LambdaTest. It automatically handles:
|
|
4
|
+
|
|
5
|
+
1. **Authentication**: Pass `username` and `accessKey` in the service options.
|
|
6
|
+
2. **Test Status Updates**: Automatically marks tests as Passed/Failed on the LambdaTest dashboard using Lambda Hooks.
|
|
7
|
+
3. **Logs**: Provides clean, colorful status logs in the terminal.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install wdio-lambdatest-service --save-dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
*(For local development, you can link the package or use the setup script described below)*
|
|
16
|
+
|
|
17
|
+
## Automated Setup
|
|
18
|
+
|
|
19
|
+
You can automatically update your existing WebdriverIO configuration files (`*.conf.js`) to use this SDK using the included setup script.
|
|
20
|
+
|
|
21
|
+
Run the following command, providing the path to your project or test folder:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# If installed via npm
|
|
25
|
+
npx wdio-lambdatest-setup ./path/to/your/project
|
|
26
|
+
|
|
27
|
+
# If running locally from source
|
|
28
|
+
node wdio-lambdatest-service/bin/setup.js ./android-sample
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This script will:
|
|
32
|
+
- Scan for `.conf.js` files recursively.
|
|
33
|
+
- Inject the service configuration.
|
|
34
|
+
- Set `logLevel` to `error` for cleaner output.
|
|
35
|
+
- Comment out hardcoded credentials (so the SDK handles them).
|
|
36
|
+
|
|
37
|
+
## Manual Configuration
|
|
38
|
+
|
|
39
|
+
Add the service to your `wdio.conf.js`:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const path = require('path');
|
|
43
|
+
|
|
44
|
+
exports.config = {
|
|
45
|
+
// ...
|
|
46
|
+
// user: process.env.LT_USERNAME, // handled by SDK
|
|
47
|
+
// key: process.env.LT_ACCESS_KEY, // handled by SDK
|
|
48
|
+
|
|
49
|
+
logLevel: 'error', // Recommended to reduce noise
|
|
50
|
+
|
|
51
|
+
services: [
|
|
52
|
+
['lambdatest', {
|
|
53
|
+
username: process.env.LT_USERNAME,
|
|
54
|
+
accessKey: process.env.LT_ACCESS_KEY
|
|
55
|
+
}]
|
|
56
|
+
],
|
|
57
|
+
// ...
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If you are using the SDK locally (without npm install), use the absolute path:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
services: [
|
|
65
|
+
[path.join(__dirname, '../../wdio-lambdatest-service'), {
|
|
66
|
+
username: process.env.LT_USERNAME,
|
|
67
|
+
accessKey: process.env.LT_ACCESS_KEY
|
|
68
|
+
}]
|
|
69
|
+
],
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Features
|
|
73
|
+
|
|
74
|
+
- **Automatic Status Updates**: Checks test results in `afterTest` (Mocha/Jasmine) or `afterScenario` (Cucumber) and updates the LambdaTest job status.
|
|
75
|
+
- **Credential Management**: Can inject credentials if not already present in the main config.
|
|
76
|
+
- **Async Support**: Works seamlessly with both Sync and Async WebdriverIO execution modes.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
This package exports a **Service** (worker) and a **Launcher** (main process).
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
const rl = readline.createInterface({
|
|
7
|
+
input: process.stdin,
|
|
8
|
+
output: process.stdout
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const config = {
|
|
12
|
+
testName: '',
|
|
13
|
+
filename: '',
|
|
14
|
+
username: '',
|
|
15
|
+
key: '',
|
|
16
|
+
type: '', // 'app' or 'browser'
|
|
17
|
+
subType: '', // 'real', 'virtual', 'desktop', 'mobile-virtual', 'mobile-real'
|
|
18
|
+
parallel: 0,
|
|
19
|
+
specPath: ''
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const outputDir = path.resolve(process.cwd(), 'LT_Test');
|
|
23
|
+
|
|
24
|
+
// Ensure output dir exists
|
|
25
|
+
if (!fs.existsSync(outputDir)) {
|
|
26
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ask(question, defaultVal) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
rl.question(`${question}${defaultVal ? ` (${defaultVal})` : ''}: `, (answer) => {
|
|
32
|
+
resolve(answer.trim() || defaultVal);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function run() {
|
|
38
|
+
console.log('\n--- LambdaTest WDIO Config Generator ---\n');
|
|
39
|
+
|
|
40
|
+
config.testName = await ask('Enter Test Name (e.g. AndroidAppTest)', 'AndroidAppTest');
|
|
41
|
+
config.filename = `${config.testName}.conf.js`;
|
|
42
|
+
|
|
43
|
+
config.username = await ask('Enter LambdaTest Username', process.env.LT_USERNAME || 'YOUR_USERNAME');
|
|
44
|
+
config.key = await ask('Enter LambdaTest Access Key', process.env.LT_ACCESS_KEY || 'YOUR_ACCESS_KEY');
|
|
45
|
+
|
|
46
|
+
const typeInput = await ask('Test Type (1: App, 2: Browser)', '1');
|
|
47
|
+
config.type = typeInput === '1' || typeInput.toLowerCase() === 'app' ? 'app' : 'browser';
|
|
48
|
+
|
|
49
|
+
if (config.type === 'app') {
|
|
50
|
+
const subTypeInput = await ask('Device Type (1: Real Device, 2: Virtual Device)', '1');
|
|
51
|
+
config.subType = subTypeInput === '1' ? 'real' : 'virtual';
|
|
52
|
+
} else {
|
|
53
|
+
console.log('Browser Options: 1: Desktop, 2: Mobile Browser (Virtual), 3: Mobile Browser (Real)');
|
|
54
|
+
const subTypeInput = await ask('Select Option', '1');
|
|
55
|
+
if (subTypeInput === '2') config.subType = 'mobile-virtual';
|
|
56
|
+
else if (subTypeInput === '3') config.subType = 'mobile-real';
|
|
57
|
+
else config.subType = 'desktop';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parallelInput = await ask('Number of Parallel Threads (0 or empty for no parallel)', '0');
|
|
61
|
+
config.parallel = parseInt(parallelInput) || 0;
|
|
62
|
+
|
|
63
|
+
config.specPath = await ask('Path to Spec File (e.g. ./specs/test.js)', './specs/android-test.js');
|
|
64
|
+
|
|
65
|
+
// Generate Config Content
|
|
66
|
+
let capabilities = [];
|
|
67
|
+
const commonCaps = {
|
|
68
|
+
build: `LT_WDIO_${config.testName}_${new Date().toISOString().split('T')[0]}`,
|
|
69
|
+
name: config.testName,
|
|
70
|
+
visual: true,
|
|
71
|
+
console: true
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (config.type === 'app') {
|
|
75
|
+
const cap = {
|
|
76
|
+
"lt:options": {
|
|
77
|
+
w3c: true,
|
|
78
|
+
platformName: "Android",
|
|
79
|
+
deviceName: config.subType === 'real' ? ".*" : "Pixel 4",
|
|
80
|
+
platformVersion: config.subType === 'real' ? undefined : "11",
|
|
81
|
+
isRealMobile: config.subType === 'real',
|
|
82
|
+
app: process.env.LT_APP_ID || "lt://proverbial-android", // Default app
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
capabilities.push(cap);
|
|
86
|
+
} else {
|
|
87
|
+
if (config.subType === 'desktop') {
|
|
88
|
+
capabilities.push({
|
|
89
|
+
browserName: "chrome",
|
|
90
|
+
browserVersion: "latest",
|
|
91
|
+
"lt:options": {
|
|
92
|
+
platformName: "Windows 10"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
// Mobile Browser
|
|
97
|
+
capabilities.push({
|
|
98
|
+
"lt:options": {
|
|
99
|
+
w3c: true,
|
|
100
|
+
platformName: "Android",
|
|
101
|
+
deviceName: config.subType === 'mobile-real' ? ".*" : "Pixel 4",
|
|
102
|
+
platformVersion: config.subType === 'mobile-real' ? undefined : "11",
|
|
103
|
+
isRealMobile: config.subType === 'mobile-real',
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Prepare template
|
|
110
|
+
const template = `const path = require('path');
|
|
111
|
+
|
|
112
|
+
exports.config = {
|
|
113
|
+
// Authentication handled by SDK
|
|
114
|
+
// user: "${config.username}",
|
|
115
|
+
// key: "${config.key}",
|
|
116
|
+
|
|
117
|
+
updateJob: false,
|
|
118
|
+
specs: ["${config.specPath}"],
|
|
119
|
+
exclude: [],
|
|
120
|
+
|
|
121
|
+
maxInstances: ${config.parallel > 0 ? config.parallel : 1},
|
|
122
|
+
|
|
123
|
+
commonCapabilities: ${JSON.stringify(commonCaps, null, 4)},
|
|
124
|
+
|
|
125
|
+
capabilities: ${JSON.stringify(capabilities, null, 4)},
|
|
126
|
+
|
|
127
|
+
logLevel: 'error',
|
|
128
|
+
coloredLogs: true,
|
|
129
|
+
screenshotPath: "./errorShots/",
|
|
130
|
+
baseUrl: "https://mobile-hub.lambdatest.com",
|
|
131
|
+
waitforTimeout: 10000,
|
|
132
|
+
connectionRetryTimeout: 90000,
|
|
133
|
+
connectionRetryCount: 3,
|
|
134
|
+
path: "/wd/hub",
|
|
135
|
+
hostname: "mobile-hub.lambdatest.com",
|
|
136
|
+
port: 80,
|
|
137
|
+
|
|
138
|
+
services: [
|
|
139
|
+
[path.join(__dirname, '../wdio-lambdatest-service'), {
|
|
140
|
+
user: "${config.username}",
|
|
141
|
+
key: "${config.key}"
|
|
142
|
+
}]
|
|
143
|
+
],
|
|
144
|
+
|
|
145
|
+
framework: "mocha",
|
|
146
|
+
mochaOpts: {
|
|
147
|
+
ui: "bdd",
|
|
148
|
+
timeout: 20000,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
exports.config.capabilities.forEach(function (caps) {
|
|
153
|
+
for (var i in exports.config.commonCapabilities)
|
|
154
|
+
caps[i] = caps[i] || exports.config.commonCapabilities[i];
|
|
155
|
+
});
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
const outputPath = path.join(outputDir, config.filename);
|
|
159
|
+
fs.writeFileSync(outputPath, template);
|
|
160
|
+
|
|
161
|
+
console.log(`\nSuccessfully created configuration file at:\n${outputPath}`);
|
|
162
|
+
console.log(`\nRun it with:\n./node_modules/.bin/wdio ${path.relative(process.cwd(), outputPath)}`);
|
|
163
|
+
|
|
164
|
+
rl.close();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
run();
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
const sdkPath = path.resolve(__dirname, '..'); // Absolute path to the SDK folder
|
|
7
|
+
|
|
8
|
+
function start() {
|
|
9
|
+
const targetDirArg = process.argv[2];
|
|
10
|
+
|
|
11
|
+
if (targetDirArg) {
|
|
12
|
+
traverseDirectory(path.resolve(targetDirArg));
|
|
13
|
+
console.log('Done scanning directories.');
|
|
14
|
+
} else {
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
rl.question('Where are your test scripts located? (e.g. ./android-sample): ', (answer) => {
|
|
21
|
+
if (!answer || answer.trim() === '') {
|
|
22
|
+
console.error('No directory provided. Exiting.');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolvedPath = path.resolve(answer.trim());
|
|
27
|
+
console.log(`Scanning: ${resolvedPath}`);
|
|
28
|
+
|
|
29
|
+
traverseDirectory(resolvedPath);
|
|
30
|
+
console.log('Done scanning directories.');
|
|
31
|
+
rl.close();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function traverseDirectory(dir) {
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
console.error(`Directory not found: ${dir}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const files = fs.readdirSync(dir);
|
|
43
|
+
|
|
44
|
+
files.forEach(file => {
|
|
45
|
+
const fullPath = path.join(dir, file);
|
|
46
|
+
const stat = fs.statSync(fullPath);
|
|
47
|
+
|
|
48
|
+
if (stat.isDirectory()) {
|
|
49
|
+
if (file !== 'node_modules' && file !== '.git') {
|
|
50
|
+
traverseDirectory(fullPath);
|
|
51
|
+
}
|
|
52
|
+
} else if (file.endsWith('.conf.js')) {
|
|
53
|
+
updateConfigFile(fullPath);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateConfigFile(filePath) {
|
|
59
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
60
|
+
|
|
61
|
+
console.log(`Processing: ${filePath}`);
|
|
62
|
+
|
|
63
|
+
// Extract credentials (either active or commented out)
|
|
64
|
+
let userVal = "process.env.LT_USERNAME";
|
|
65
|
+
let keyVal = "process.env.LT_ACCESS_KEY";
|
|
66
|
+
|
|
67
|
+
const userMatch = content.match(/^\s*\/{0,2}\s*user:\s*(.*?),/m);
|
|
68
|
+
if (userMatch) userVal = userMatch[1].trim();
|
|
69
|
+
|
|
70
|
+
const keyMatch = content.match(/^\s*\/{0,2}\s*key:\s*(.*?),/m);
|
|
71
|
+
if (keyMatch) keyVal = keyMatch[1].trim();
|
|
72
|
+
|
|
73
|
+
// Calculate relative path
|
|
74
|
+
let relativeSdkPath = path.relative(path.dirname(filePath), sdkPath);
|
|
75
|
+
relativeSdkPath = relativeSdkPath.replace(/\\/g, '/');
|
|
76
|
+
|
|
77
|
+
// Construct the correct service entry with credentials
|
|
78
|
+
const serviceEntry = `[path.join(__dirname, '${relativeSdkPath}'), { user: ${userVal}, key: ${keyVal} }]`;
|
|
79
|
+
|
|
80
|
+
// Check if already updated (but maybe with missing credentials)
|
|
81
|
+
if (content.includes('wdio-lambdatest-service')) {
|
|
82
|
+
// Fix empty options if present: [path..., {}]
|
|
83
|
+
const emptyOptionsRegex = /\[path\.join\(__dirname, '.*?wdio-lambdatest-service'\), \{\}\]/;
|
|
84
|
+
if (emptyOptionsRegex.test(content)) {
|
|
85
|
+
console.log(`Fixing missing credentials in: ${filePath}`);
|
|
86
|
+
content = content.replace(emptyOptionsRegex, serviceEntry);
|
|
87
|
+
|
|
88
|
+
// Ensure logLevel is error
|
|
89
|
+
content = content.replace(/logLevel: ['"]info['"]/, "logLevel: 'error'");
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(filePath, content);
|
|
92
|
+
console.log(`Successfully repaired ${filePath}`);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`Skipping (already correct): ${filePath}`);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`Updating: ${filePath}`);
|
|
100
|
+
|
|
101
|
+
// 1. Ensure 'path' module is required
|
|
102
|
+
if (!content.includes("require('path')")) {
|
|
103
|
+
content = "const path = require('path');\n" + content;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. Inject Services
|
|
107
|
+
if (content.includes('services: [')) {
|
|
108
|
+
content = content.replace('services: [', `services: [\n ${serviceEntry},`);
|
|
109
|
+
} else {
|
|
110
|
+
content = content.replace('exports.config = {', `exports.config = {\n services: [\n ${serviceEntry}\n ],`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 4. Set logLevel to 'error'
|
|
114
|
+
if (content.includes('logLevel:')) {
|
|
115
|
+
content = content.replace(/logLevel:.*,/, "logLevel: 'error',");
|
|
116
|
+
} else {
|
|
117
|
+
content = content.replace('exports.config = {', `exports.config = {\n logLevel: 'error',`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 5. Comment out hardcoded user/key
|
|
121
|
+
// Use replace with callback to avoid re-commenting
|
|
122
|
+
content = content.replace(/^(\s*)user:/gm, '$1// user:');
|
|
123
|
+
content = content.replace(/^(\s*)key:/gm, '$1// key:');
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(filePath, content);
|
|
126
|
+
console.log(`Successfully updated ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
start();
|
package/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const LambdaTestService = require('./src/service');
|
|
2
|
+
const LambdaTestLauncher = require('./src/launcher');
|
|
3
|
+
|
|
4
|
+
// Export the Service class as the main module export
|
|
5
|
+
module.exports = LambdaTestService;
|
|
6
|
+
|
|
7
|
+
// Also export as 'default' for compatibility with some WDIO loaders
|
|
8
|
+
module.exports.default = LambdaTestService;
|
|
9
|
+
|
|
10
|
+
// Export the Launcher
|
|
11
|
+
module.exports.launcher = LambdaTestLauncher;
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wdio-lambdatest-service-sdk",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "WebdriverIO service for LambdaTest integration",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wdio-lambdatest-setup": "bin/setup.js",
|
|
8
|
+
"wdio-lambdatest-generator": "bin/generate-config.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"webdriverio",
|
|
15
|
+
"wdio",
|
|
16
|
+
"lambdatest",
|
|
17
|
+
"service",
|
|
18
|
+
"sdk"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@wdio/cli": ">=5.0.0",
|
|
24
|
+
"webdriverio": ">=5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=10.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/launcher.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = class LambdaTestLauncher {
|
|
3
|
+
constructor(serviceOptions, capabilities, config) {
|
|
4
|
+
this.options = serviceOptions || {};
|
|
5
|
+
console.log('[LambdaTest Service] Launcher initialized with options:', JSON.stringify(this.options));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
onPrepare(config, capabilities) {
|
|
9
|
+
console.log('[LambdaTest Service] Processing credentials...');
|
|
10
|
+
|
|
11
|
+
// 1. Update Main Config (for WDIO Basic Auth)
|
|
12
|
+
if (this.options.username) config.user = this.options.username;
|
|
13
|
+
if (this.options.accessKey) config.key = this.options.accessKey;
|
|
14
|
+
if (this.options.user) config.user = this.options.user;
|
|
15
|
+
if (this.options.key) config.key = this.options.key;
|
|
16
|
+
|
|
17
|
+
// Fallback to Env Vars
|
|
18
|
+
if (!config.user) config.user = process.env.LT_USERNAME;
|
|
19
|
+
if (!config.key) config.key = process.env.LT_ACCESS_KEY;
|
|
20
|
+
|
|
21
|
+
// 2. Update Capabilities (Explicitly pass credentials to caps)
|
|
22
|
+
// This ensures that even if Basic Auth fails or is missed,
|
|
23
|
+
// the grid sees them in the capabilities.
|
|
24
|
+
const user = config.user;
|
|
25
|
+
const key = config.key;
|
|
26
|
+
|
|
27
|
+
if (user && key) {
|
|
28
|
+
const capsList = Array.isArray(capabilities) ? capabilities : [capabilities];
|
|
29
|
+
capsList.forEach(cap => {
|
|
30
|
+
// Legacy JSONWP
|
|
31
|
+
if (!cap.user) cap.user = user;
|
|
32
|
+
if (!cap.accessKey) cap.accessKey = key;
|
|
33
|
+
|
|
34
|
+
// W3C Support (lt:options)
|
|
35
|
+
if (cap['lt:options']) {
|
|
36
|
+
if (!cap['lt:options'].username) cap['lt:options'].username = user;
|
|
37
|
+
if (!cap['lt:options'].accessKey) cap['lt:options'].accessKey = key;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
console.log('[LambdaTest Service] Credentials injected into config and capabilities.');
|
|
41
|
+
} else {
|
|
42
|
+
console.error('[LambdaTest Service] Credentials not found! Please check service options or env vars.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
package/src/service.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = class LambdaTestService {
|
|
3
|
+
constructor(serviceOptions, capabilities, config) {
|
|
4
|
+
this.options = serviceOptions || {};
|
|
5
|
+
this.isEnabled = true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
before(capabilities, specs) {
|
|
9
|
+
console.log('[LambdaTest Service] Service initialized in worker process.');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Triggered after each test (Mocha/Jasmine)
|
|
14
|
+
*/
|
|
15
|
+
async afterTest(test, context, result) {
|
|
16
|
+
// Handle WDIO v5/v6/v7 differences
|
|
17
|
+
let passed = false;
|
|
18
|
+
let error = undefined;
|
|
19
|
+
let title = 'Test';
|
|
20
|
+
|
|
21
|
+
// Check if result is the object { error, result, duration, passed, retries }
|
|
22
|
+
if (result && typeof result.passed === 'boolean') {
|
|
23
|
+
passed = result.passed;
|
|
24
|
+
error = result.error;
|
|
25
|
+
}
|
|
26
|
+
// Fallback for older/different versions where result might be the passed boolean or different structure
|
|
27
|
+
else if (test && typeof test.passed === 'boolean') {
|
|
28
|
+
passed = test.passed;
|
|
29
|
+
error = test.error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (test && test.title) title = test.title;
|
|
33
|
+
|
|
34
|
+
await this.updateStatus(passed, error ? (error.message || error) : undefined, title);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Triggered after each scenario (Cucumber)
|
|
39
|
+
*/
|
|
40
|
+
async afterScenario(uri, feature, scenario, result) {
|
|
41
|
+
const isPassed = result.status === 'passed' || result === 'passed' || result.passed === true;
|
|
42
|
+
const errorMessage = result.error ? result.error : (isPassed ? undefined : 'Scenario Failed');
|
|
43
|
+
const title = scenario.name || 'Scenario';
|
|
44
|
+
await this.updateStatus(isPassed, errorMessage, title);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Updates the test status on LambdaTest
|
|
49
|
+
* @param {boolean} passed
|
|
50
|
+
* @param {string} errorMessage
|
|
51
|
+
* @param {string} testTitle
|
|
52
|
+
*/
|
|
53
|
+
async updateStatus(passed, errorMessage, testTitle) {
|
|
54
|
+
if (!this.isEnabled) return;
|
|
55
|
+
|
|
56
|
+
const status = passed ? 'passed' : 'failed';
|
|
57
|
+
const remark = errorMessage || (passed ? 'Test Passed' : 'Test Failed');
|
|
58
|
+
|
|
59
|
+
// ANSI Color Codes
|
|
60
|
+
const reset = "\x1b[0m";
|
|
61
|
+
const green = "\x1b[32m";
|
|
62
|
+
const red = "\x1b[31m";
|
|
63
|
+
const cyan = "\x1b[36m";
|
|
64
|
+
const gray = "\x1b[90m";
|
|
65
|
+
|
|
66
|
+
const color = passed ? green : red;
|
|
67
|
+
const icon = passed ? '✔' : '✖';
|
|
68
|
+
|
|
69
|
+
console.log(`\n${cyan}[LambdaTest SDK]${reset} ${gray}Updating Status...${reset}`);
|
|
70
|
+
console.log(`${cyan}[LambdaTest SDK]${reset} Test: ${testTitle}`);
|
|
71
|
+
console.log(`${cyan}[LambdaTest SDK]${reset} Status: ${color}${status.toUpperCase()} ${icon}${reset}`);
|
|
72
|
+
if (!passed) {
|
|
73
|
+
console.log(`${cyan}[LambdaTest SDK]${reset} Reason: ${red}${remark}${reset}`);
|
|
74
|
+
}
|
|
75
|
+
console.log(''); // Empty line
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Using browser.execute to trigger the Lambda Hook
|
|
79
|
+
const script = `lambda-hook: ${JSON.stringify({
|
|
80
|
+
action: 'setTestStatus',
|
|
81
|
+
arguments: {
|
|
82
|
+
status: status,
|
|
83
|
+
remark: remark
|
|
84
|
+
}
|
|
85
|
+
})}`;
|
|
86
|
+
|
|
87
|
+
// Await the execution in case we are in async mode
|
|
88
|
+
await browser.execute(script);
|
|
89
|
+
|
|
90
|
+
// Add a small pause to ensure the hook is processed before session ends
|
|
91
|
+
await browser.pause(1000);
|
|
92
|
+
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(`${red}[LambdaTest SDK] Failed to update test status via hook.${reset}`, e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|