Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c748c5a

Browse files
authoredApr 29, 2025
Merge pull request #283 from LambdaTest/stage
Release PR: version 4.1.10 [Implement node tunnel in SmartUI CLI]
2 parents 771ed4d + b5cdaaa commit c748c5a

11 files changed

+353
-63
lines changed
 

‎package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "4.1.9",
3+
"version": "4.1.10",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"
@@ -30,6 +30,7 @@
3030
"@types/cross-spawn": "^6.0.4",
3131
"@types/node": "^20.8.9",
3232
"@types/which": "^3.0.2",
33+
"@lambdatest/node-tunnel": "^4.0.9",
3334
"ajv": "^8.12.0",
3435
"ajv-errors": "^3.0.0",
3536
"axios": "^1.6.0",

‎src/commander/exec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import exec from '../tasks/exec.js'
1111
import processSnapshots from '../tasks/processSnapshot.js'
1212
import finalizeBuild from '../tasks/finalizeBuild.js'
1313
import snapshotQueue from '../lib/snapshotQueue.js'
14+
import startTunnel from '../tasks/startTunnel.js'
1415

1516
const command = new Command();
1617

@@ -42,6 +43,7 @@ command
4243
authExec(ctx),
4344
startServer(ctx),
4445
getGitInfo(ctx),
46+
...(ctx.config.tunnel && ctx.config.tunnel?.type === 'auto' ? [startTunnel(ctx)] : []),
4547
createBuildExec(ctx),
4648
exec(ctx),
4749
processSnapshots(ctx),
@@ -63,6 +65,7 @@ command
6365
await tasks.run(ctx);
6466
} catch (error) {
6567
ctx.log.info('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
68+
throw new Error();
6669
}
6770
})
6871

‎src/lib/ctx.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context, Env, WebConfig, MobileConfig, basicAuth } from '../types.js'
1+
import { Context, Env, WebConfig, MobileConfig, basicAuth, tunnelConfig } from '../types.js'
22
import constants from './constants.js'
33
import { version } from '../../package.json'
44
import { validateConfig } from './schemaValidation.js'
@@ -12,6 +12,7 @@ export default (options: Record<string, string>): Context => {
1212
let webConfig: WebConfig;
1313
let mobileConfig: MobileConfig;
1414
let basicAuthObj: basicAuth
15+
let tunnelObj: tunnelConfig
1516
let config = constants.DEFAULT_CONFIG;
1617
let port: number;
1718
let resolutionOff: boolean;
@@ -77,7 +78,10 @@ export default (options: Record<string, string>): Context => {
7778
}
7879
if (config.basicAuthorization) {
7980
basicAuthObj = config.basicAuthorization
80-
}
81+
}
82+
if (config.tunnel) {
83+
tunnelObj = config.tunnel
84+
}
8185

8286
return {
8387
env: env,
@@ -100,8 +104,7 @@ export default (options: Record<string, string>): Context => {
100104
useGlobalCache: config.useGlobalCache ?? false,
101105
ignoreHTTPSErrors: config.ignoreHTTPSErrors ?? false,
102106
skipBuildCreation: config.skipBuildCreation ?? false,
103-
tunnel: config.tunnel ?? false,
104-
tunnelName: config.tunnelName || '',
107+
tunnel: tunnelObj,
105108
userAgent: config.userAgent || ''
106109
},
107110
uploadFilePath: '',
@@ -144,6 +147,8 @@ export default (options: Record<string, string>): Context => {
144147
isSnapshotCaptured: false,
145148
sessionCapabilitiesMap: new Map<string, any[]>(),
146149
buildToSnapshotCountMap: new Map<string, number>(),
147-
fetchResultsForBuild: new Array<string>
150+
fetchResultsForBuild: new Array<string>,
151+
orgId: 0,
152+
userId: 0
148153
}
149154
}

‎src/lib/httpClient.ts

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ export default class httpClient {
4545

4646
this.axiosInstance = axios.create(axiosConfig);
4747

48-
4948
this.axiosInstance.interceptors.request.use((config) => {
50-
if (!config.headers['projectToken']) {
49+
if (!config.headers['projectToken'] && this.projectToken !== '') {
5150
config.headers['projectToken'] = this.projectToken;
51+
} else if ((!config.headers['projectName'] && this.projectName !== '')) {
52+
config.headers['projectName'] = this.projectName;
53+
if (!config.headers['username'] || config.headers['username'] === '') {
54+
config.headers['username'] = this.username;
55+
}
56+
if (!config.headers['accessKey'] || config.headers['accessKey'] === '') {
57+
config.headers['accessKey'] = this.accessKey;
58+
}
5259
}
53-
config.headers['projectName'] = this.projectName;
54-
config.headers['username'] = this.username;
55-
config.headers['accessKey'] = this.accessKey;
5660
return config;
5761
});
5862

@@ -151,6 +155,53 @@ export default class httpClient {
151155
}
152156
}
153157

158+
async authExec(ctx: Context, log: Logger, env: Env): Promise<{ authResult: number, orgId: number, userId: number }> {
159+
let authResult = 1;
160+
let userName = '';
161+
let passWord = '';
162+
if (ctx.config.tunnel) {
163+
if (ctx.config.tunnel?.user && ctx.config.tunnel?.key) {
164+
userName = ctx.config.tunnel.user
165+
passWord = ctx.config.tunnel.key
166+
} else {
167+
userName = this.username
168+
passWord = this.accessKey
169+
}
170+
}
171+
if (this.projectToken) {
172+
authResult = 0;
173+
}
174+
const response = await this.request({
175+
url: '/token/verify',
176+
method: 'GET',
177+
headers: {
178+
username: userName,
179+
accessKey: passWord
180+
}
181+
}, log);
182+
if (response && response.projectToken) {
183+
let orgId = 0;
184+
let userId = 0;
185+
this.projectToken = response.projectToken;
186+
env.PROJECT_TOKEN = response.projectToken;
187+
if (response.message && response.message.includes('Project created successfully')) {
188+
authResult = 2;
189+
}
190+
if (response.orgId) {
191+
orgId = response.orgId
192+
}
193+
if (response.userId) {
194+
userId = response.userId
195+
}
196+
return { authResult, orgId, userId };
197+
} else {
198+
if(response && response.message) {
199+
throw new Error(response.message);
200+
}
201+
throw new Error('Authentication failed, project token not received. Refer to the smartui.log file for more information');
202+
}
203+
}
204+
154205
createBuild(git: Git, config: any, log: Logger, buildName: string, isStartExec: boolean) {
155206
return this.request({
156207
url: '/build',
@@ -174,16 +225,24 @@ export default class httpClient {
174225
}, log);
175226
}
176227

177-
getTunnelDetails(tunnelName: string, log: Logger) {
228+
getTunnelDetails(ctx: Context, log: Logger) {
229+
const data: any = {
230+
orgId: ctx.orgId,
231+
userId: ctx.userId,
232+
userName: ctx.config.tunnel?.user,
233+
password: ctx.config.tunnel?.key
234+
};
235+
236+
if (ctx.config.tunnel?.tunnelName) {
237+
data.tunnelName = ctx.config.tunnel.tunnelName;
238+
}
239+
178240
return this.request({
179241
url: '/tunnel',
180242
method: 'POST',
181-
data: {
182-
tunnelName: tunnelName
183-
}
184-
}, log)
243+
data: data
244+
}, log);
185245
}
186-
187246

188247
ping(buildId: string, log: Logger) {
189248
return this.request({

‎src/lib/schemaValidation.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,60 @@ const ConfigSchema = {
189189
errorMessage: "Invalid config; skipBuildCreation must be true/false"
190190
},
191191
tunnel: {
192-
type: "boolean",
193-
errorMessage: "Invalid config; tunnel must be true/false"
194-
},
195-
tunnelName: {
196-
type: "string",
197-
errorMessage: "Invalid config; tunnelName must be string"
192+
type: "object",
193+
properties: {
194+
type: {
195+
type: "string",
196+
enum: ["auto", "manual"],
197+
errorMessage: "Invalid config; tunnel type is mandatory parameter of type string having value auto or manual",
198+
},
199+
tunnelName: {
200+
type: "string",
201+
errorMessage: "Invalid config; tunnelName should be a string value"
202+
},
203+
user: {
204+
type: "string",
205+
errorMessage: "Invalid config; user should be a string value"
206+
},
207+
key: {
208+
type: "string",
209+
errorMessage: "Invalid config; key should be a string value"
210+
},
211+
port: {
212+
type: "string",
213+
errorMessage: "Invalid config; port should be a string value"
214+
},
215+
proxyHost: {
216+
type: "string",
217+
errorMessage: "Invalid config; proxyHost should be a string value"
218+
},
219+
proxyPort: {
220+
type: "number",
221+
errorMessage: "Invalid config; proxyPort should be an int value"
222+
},
223+
proxyUser: {
224+
type: "string",
225+
errorMessage: "Invalid config; proxyUser should be a string value"
226+
},
227+
proxyPass: {
228+
type: "string",
229+
errorMessage: "Invalid config; proxyPass should be a string value"
230+
},
231+
dir: {
232+
type: "string",
233+
errorMessage: "Invalid config; dir should be a string value"
234+
},
235+
v: {
236+
type: "boolean",
237+
errorMessage: "Invalid config; v should be a boolean value"
238+
},
239+
logFile: {
240+
type: "string",
241+
errorMessage: "Invalid config; logFile should be a string value"
242+
},
243+
},
244+
required: ["type"],
245+
additionalProperties: false
198246
},
199247
userAgent: {
200248
type: "string",

‎src/lib/snapshotQueue.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Snapshot, Context } from "../types.js";
33
import constants from "./constants.js";
44
import processSnapshot from "./processSnapshot.js"
55
import { v4 as uuidv4 } from 'uuid';
6-
import { startPolling } from "./utils.js";
6+
import { startPolling, stopTunnelHelper } from "./utils.js";
77

88
export default class Queue {
99
private snapshots: Array<Snapshot> = [];
@@ -272,7 +272,7 @@ export default class Queue {
272272
this.processingSnapshot = snapshot?.name;
273273
let drop = false;
274274

275-
if (this.ctx.isStartExec) {
275+
if (this.ctx.isStartExec && !this.ctx.config.tunnel) {
276276
this.ctx.log.info(`Processing Snapshot: ${snapshot?.name}`);
277277
}
278278

@@ -344,6 +344,9 @@ export default class Queue {
344344
useKafkaFlow: resp.data.useKafkaFlow || false,
345345
}
346346
} else {
347+
if (this.ctx.config.tunnel && this.ctx.config.tunnel?.type === 'auto') {
348+
await stopTunnelHelper(this.ctx)
349+
}
347350
throw new Error('SmartUI capabilities are missing in env variables or in driver capabilities');
348351
}
349352
if (this.ctx.options.fetchResults) {
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.