Skip to content

Commit c7f095e

Browse files
Merge pull request #371 from LambdaTest/stage
Release v4.1.32
2 parents cf0cdb3 + aa0f6c2 commit c7f095e

17 files changed

+193
-70
lines changed

package.json

Lines changed: 4 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.31",
3+
"version": "4.1.32",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"
@@ -49,6 +49,9 @@
4949
"which": "^4.0.0",
5050
"winston": "^3.10.0"
5151
},
52+
"overrides": {
53+
"simple-swizzle": "0.2.2"
54+
},
5255
"devDependencies": {
5356
"typescript": "^5.3.2"
5457
}

src/commander/exec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ command
4040
ctx.args.execCommand = execCommand
4141
ctx.snapshotQueue = new snapshotQueue(ctx)
4242
ctx.totalSnapshots = 0
43+
ctx.sourceCommand = 'exec'
4344

4445
let tasks = new Listr<Context>(
4546
[

src/commander/server.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import getGitInfo from '../tasks/getGitInfo.js';
88
import createBuildExec from '../tasks/createBuildExec.js';
99
import snapshotQueue from '../lib/snapshotQueue.js';
1010
import { startPolling, startPingPolling } from '../lib/utils.js';
11+
import startTunnel from '../tasks/startTunnel.js'
1112

1213
const command = new Command();
1314

@@ -27,12 +28,14 @@ command
2728
ctx.snapshotQueue = new snapshotQueue(ctx);
2829
ctx.totalSnapshots = 0
2930
ctx.isStartExec = true
30-
31+
ctx.sourceCommand = 'exec-start'
32+
3133
let tasks = new Listr<Context>(
3234
[
3335
authExec(ctx),
3436
startServer(ctx),
3537
getGitInfo(ctx),
38+
...(ctx.config.tunnel && ctx.config.tunnel?.type === 'auto' ? [startTunnel(ctx)] : []),
3639
createBuildExec(ctx),
3740

3841
],
@@ -50,15 +53,16 @@ command
5053

5154
try {
5255
await tasks.run(ctx);
53-
if (ctx.build && ctx.build.id) {
56+
if (ctx.build && ctx.build.id && !ctx.autoTunnelStarted) {
5457
startPingPolling(ctx);
5558
}
5659
if (ctx.options.fetchResults && ctx.build && ctx.build.id) {
5760
startPolling(ctx, '', false, '')
5861
}
59-
62+
6063
} catch (error) {
6164
console.error('Error during server execution:', error);
65+
process.exit(1);
6266
}
6367
});
6468

src/commander/stopServer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ command
3333
}
3434
} catch (error: any) {
3535
// Handle any errors during the HTTP request
36-
if (error.code === 'ECONNABORTED') {
36+
if (error && error.code === 'ECONNABORTED') {
3737
console.error(chalk.red('Error: SmartUI server did not respond in 15 seconds'));
38+
} if (error && error.code === 'ECONNREFUSED') {
39+
console.error(chalk.red('Error: Looks like smartui cli server is already stopped'));
3840
} else {
3941
console.error(chalk.red('Error while stopping server'));
4042
}

src/lib/ctx.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export default (options: Record<string, string>): Context => {
3939
delete config.web.resolutions;
4040
}
4141

42+
if(config.approvalThreshold && config.rejectionThreshold) {
43+
if(config.rejectionThreshold <= config.approvalThreshold) {
44+
throw new Error('Invalid config; rejectionThreshold must be greater than approvalThreshold');
45+
}
46+
}
47+
4248
let validateConfigFn = options.scheduled ? validateConfigForScheduled : validateConfig;
4349

4450
// validate config
@@ -142,6 +148,8 @@ export default (options: Record<string, string>): Context => {
142148
useLambdaInternal: useLambdaInternal,
143149
useExtendedViewport: useExtendedViewport,
144150
loadDomContent: loadDomContent,
151+
approvalThreshold: config.approvalThreshold,
152+
rejectionThreshold: config.rejectionThreshold,
145153
},
146154
uploadFilePath: '',
147155
webStaticConfig: [],

src/lib/httpClient.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default class httpClient {
7777
(response) => response,
7878
async (error) => {
7979
const { config } = error;
80-
if (config && config.url === '/screenshot' && config.method === 'post') {
80+
if (config && config.url === '/screenshot' && config.method === 'post' && error?.response?.status !== 401) {
8181
// Set default retry count and delay if not already defined
8282
if (!config.retryCount) {
8383
config.retryCount = 0;
@@ -242,11 +242,12 @@ export default class httpClient {
242242
}, log)
243243
}
244244

245-
getScreenshotData(buildId: string, baseline: boolean, log: Logger, projectToken: string) {
245+
getScreenshotData(buildId: string, baseline: boolean, log: Logger, projectToken: string, buildName: string) {
246+
log.debug(`Fetching screenshot data for buildId: ${buildId} having buildName: ${buildName} with baseline: ${baseline}`);
246247
return this.request({
247248
url: '/screenshot',
248249
method: 'GET',
249-
params: { buildId, baseline },
250+
params: { buildId, baseline, buildName },
250251
headers: {projectToken: projectToken}
251252
}, log);
252253
}
@@ -281,7 +282,7 @@ export default class httpClient {
281282
}
282283

283284

284-
getSmartUICapabilities(sessionId: string, config: any, git: any, log: Logger) {
285+
getSmartUICapabilities(sessionId: string, config: any, git: any, log: Logger, isStartExec: boolean) {
285286
return this.request({
286287
url: '/sessions/capabilities',
287288
method: 'GET',
@@ -290,7 +291,8 @@ export default class httpClient {
290291
},
291292
data: {
292293
git,
293-
config
294+
config,
295+
isStartExec
294296
},
295297
headers: {
296298
projectToken: '',
@@ -343,24 +345,33 @@ export default class httpClient {
343345
}, ctx.log)
344346
}
345347

346-
processSnapshot(ctx: Context, snapshot: ProcessedSnapshot, snapshotUuid: string, discoveryErrors: DiscoveryErrors, variantCount: number, sync: boolean = false) {
348+
processSnapshot(ctx: Context, snapshot: ProcessedSnapshot, snapshotUuid: string, discoveryErrors: DiscoveryErrors, variantCount: number, sync: boolean = false, approvalThreshold: number| undefined, rejectionThreshold: number| undefined) {
349+
const requestData: any = {
350+
name: snapshot.name,
351+
url: snapshot.url,
352+
snapshotUuid: snapshotUuid,
353+
variantCount: variantCount,
354+
test: {
355+
type: ctx.testType,
356+
source: 'cli'
357+
},
358+
discoveryErrors: discoveryErrors,
359+
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
360+
sync: sync
361+
};
362+
363+
if (approvalThreshold !== undefined) {
364+
requestData.approvalThreshold = approvalThreshold;
365+
}
366+
if (rejectionThreshold !== undefined) {
367+
requestData.rejectionThreshold = rejectionThreshold;
368+
}
369+
347370
return this.request({
348371
url: `/build/${ctx.build.id}/snapshot`,
349372
method: 'POST',
350373
headers: { 'Content-Type': 'application/json' },
351-
data: {
352-
name: snapshot.name,
353-
url: snapshot.url,
354-
snapshotUuid: snapshotUuid,
355-
variantCount: variantCount,
356-
test: {
357-
type: ctx.testType,
358-
source: 'cli'
359-
},
360-
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
361-
discoveryErrors: discoveryErrors,
362-
sync: sync
363-
}
374+
data: requestData
364375
}, ctx.log)
365376
}
366377

src/lib/schemaValidation.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ const ConfigSchema = {
239239
type: "string",
240240
errorMessage: "Invalid config; logFile should be a string value"
241241
},
242+
environment: {
243+
type: "string",
244+
enum: ["stage", "prod"],
245+
errorMessage: "Invalid config; environment should be a string value either stage or prod"
246+
}
242247
},
243248
required: ["type"],
244249
additionalProperties: false
@@ -274,6 +279,18 @@ const ConfigSchema = {
274279
loadDomContent: {
275280
type: "boolean",
276281
errorMessage: "Invalid config; loadDomContent must be true/false"
282+
},
283+
approvalThreshold: {
284+
type: "number",
285+
minimum: 0,
286+
maximum: 100,
287+
errorMessage: "Invalid config; approvalThreshold must be a number"
288+
},
289+
rejectionThreshold: {
290+
type: "number",
291+
minimum: 0,
292+
maximum: 100,
293+
errorMessage: "Invalid config; rejectionThreshold must be a number"
277294
}
278295
},
279296
anyOf: [
@@ -537,6 +554,18 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
537554
useExtendedViewport: {
538555
type: "boolean",
539556
errorMessage: "Invalid snapshot options; useExtendedViewport must be a boolean"
557+
},
558+
approvalThreshold: {
559+
type: "number",
560+
minimum: 0,
561+
maximum: 100,
562+
errorMessage: "Invalid snapshot options; approvalThreshold must be a number between 0 and 100"
563+
},
564+
rejectionThreshold: {
565+
type: "number",
566+
minimum: 0,
567+
maximum: 100,
568+
errorMessage: "Invalid snapshot options; rejectionThreshold must be a number between 0 and 100"
540569
}
541570
},
542571
additionalProperties: false

src/lib/server.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import fastify, { FastifyInstance, RouteShorthandOptions } from 'fastify';
44
import { readFileSync, truncate } from 'fs'
55
import { Context } from '../types.js'
66
import { validateSnapshot } from './schemaValidation.js'
7-
import { pingIntervalId } from './utils.js';
8-
import { startPolling } from './utils.js';
7+
import { pingIntervalId, startPollingForTunnel, stopTunnelHelper, isTunnelPolling } from './utils.js';
98

109
const uploadDomToS3ViaEnv = process.env.USE_LAMBDA_INTERNAL || false;
1110
export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMessage, ServerResponse>> => {
@@ -38,6 +37,12 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
3837
try {
3938
let { snapshot, testType } = request.body;
4039
if (!validateSnapshot(snapshot)) throw new Error(validateSnapshot.errors[0].message);
40+
41+
if(snapshot?.options?.approvalThreshold !== undefined && snapshot?.options?.rejectionThreshold !== undefined) {
42+
if(snapshot?.options?.rejectionThreshold <= snapshot?.options?.approvalThreshold) {
43+
throw new Error(`Invalid snapshot options; rejectionThreshold (${snapshot.options.rejectionThreshold}) must be greater than approvalThreshold (${snapshot.options.approvalThreshold})`);
44+
}
45+
}
4146

4247
// Fetch sessionId from snapshot options if present
4348
const sessionId = snapshot?.options?.sessionId;
@@ -53,7 +58,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
5358
} else {
5459
// If not cached, fetch from API and cache it
5560
try {
56-
let fetchedCapabilitiesResp = await ctx.client.getSmartUICapabilities(sessionId, ctx.config, ctx.git, ctx.log);
61+
let fetchedCapabilitiesResp = await ctx.client.getSmartUICapabilities(sessionId, ctx.config, ctx.git, ctx.log, ctx.isStartExec);
5762
capsBuildId = fetchedCapabilitiesResp?.buildId || ''
5863
ctx.log.debug(`fetch caps for sessionId: ${sessionId} are ${JSON.stringify(fetchedCapabilitiesResp)}`)
5964
if (capsBuildId) {
@@ -106,6 +111,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
106111
let replyCode: number;
107112
let replyBody: Record<string, any>;
108113
try {
114+
ctx.log.info('Received stop command. Finalizing build ...');
109115
if(ctx.config.delayedUpload){
110116
ctx.log.debug("started after processing because of delayedUpload")
111117
ctx.snapshotQueue?.startProcessingfunc()
@@ -118,6 +124,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
118124
}
119125
}, 1000);
120126
})
127+
let buildUrls = `build url: ${ctx.build.url}\n`;
121128

122129
for (const [sessionId, capabilities] of ctx.sessionCapabilitiesMap.entries()) {
123130
try {
@@ -126,9 +133,12 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
126133
const totalSnapshots = capabilities?.snapshotCount || 0;
127134
const sessionBuildUrl = capabilities?.buildURL || '';
128135
const testId = capabilities?.id || '';
129-
136+
ctx.log.debug(`Capabilities for sessionId ${sessionId}: ${JSON.stringify(capabilities)}`)
130137
if (buildId && projectToken) {
131138
await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
139+
if (ctx.autoTunnelStarted) {
140+
await startPollingForTunnel(ctx, buildId, false, projectToken, capabilities?.buildName);
141+
}
132142
}
133143

134144
if (testId && buildId) {
@@ -151,6 +161,16 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
151161
}
152162
}
153163

164+
165+
//If Tunnel Details are present, start polling for tunnel status
166+
if (ctx.tunnelDetails && ctx.tunnelDetails.tunnelHost != "" && ctx.build?.id) {
167+
await startPollingForTunnel(ctx, ctx.build.id, false, '', '');
168+
}
169+
//stop the tunnel if it was auto started and no tunnel polling is active
170+
if (ctx.autoTunnelStarted && isTunnelPolling === null) {
171+
await stopTunnelHelper(ctx);
172+
}
173+
154174
await ctx.browser?.close();
155175
if (ctx.server){
156176
ctx.server.close();
@@ -168,7 +188,9 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
168188
replyCode = 500;
169189
replyBody = { error: { message: error.message } };
170190
}
171-
191+
192+
ctx.log.info('Stop command processed. Tearing down server.');
193+
172194
// Step 5: Return the response
173195
return reply.code(replyCode).send(replyBody);
174196
});

src/lib/snapshotQueue.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ export default class Queue {
380380
useKafkaFlow: resp.data.useKafkaFlow || false,
381381
}
382382
} else {
383-
if (this.ctx.config.tunnel && this.ctx.config.tunnel?.type === 'auto') {
383+
if (this.ctx.autoTunnelStarted) {
384384
await stopTunnelHelper(this.ctx)
385385
}
386386
throw new Error('SmartUI capabilities are missing in env variables or in driver capabilities');
@@ -427,7 +427,9 @@ export default class Queue {
427427
}
428428
this.processNext();
429429
} else {
430-
await this.ctx.client.processSnapshot(this.ctx, processedSnapshot, snapshotUuid, discoveryErrors,calculateVariantCountFromSnapshot(processedSnapshot, this.ctx.config),snapshot?.options?.sync);
430+
let approvalThreshold = snapshot?.options?.approvalThreshold || this.ctx.config.approvalThreshold;
431+
let rejectionThreshold = snapshot?.options?.rejectionThreshold || this.ctx.config.rejectionThreshold;
432+
await this.ctx.client.processSnapshot(this.ctx, processedSnapshot, snapshotUuid, discoveryErrors,calculateVariantCountFromSnapshot(processedSnapshot, this.ctx.config),snapshot?.options?.sync, approvalThreshold, rejectionThreshold);
431433
if(snapshot?.options?.contextId && this.ctx.contextToSnapshotMap?.has(snapshot.options.contextId)){
432434
this.ctx.contextToSnapshotMap.set(snapshot.options.contextId, 1);
433435
}

src/lib/uploadAppFigma.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default async (ctx: Context): Promise<string> => {
1919
smartIgnore: ctx.config.smartIgnore,
2020
git: ctx.git,
2121
platformType: 'app',
22+
markBaseline: ctx.options.markBaseline,
2223
};
2324

2425
const responseData = await ctx.client.processWebFigma(requestBody, ctx.log);

0 commit comments

Comments
 (0)