Skip to content

Commit 6430da9

Browse files
committed
fixed merge conflicts
2 parents 529eda8 + deac3fa commit 6430da9

File tree

12 files changed

+191
-19
lines changed

12 files changed

+191
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
dist
33
.idea
4+
*.tgz

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "4.1.20",
3+
"version": "4.1.25",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"

src/commander/capture.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ command
2020
.option('-F, --force', 'forcefully apply the specified parallel instances per browser')
2121
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
2222
.option('--buildName <string>', 'Specify the build name')
23+
.option('--scheduled <string>', 'Specify the schedule ID')
2324
.option('--userName <string>', 'Specify the LT username')
2425
.option('--accessKey <string>', 'Specify the LT accesskey')
2526
.action(async function(file, _, command) {
@@ -38,7 +39,6 @@ command
3839
try {
3940
ctx.webStaticConfig = JSON.parse(fs.readFileSync(file, 'utf8'));
4041
if (!validateWebStaticConfig(ctx.webStaticConfig)){
41-
4242
ctx.log.debug(JSON.stringify(validateWebStaticConfig.errors, null, 2));
4343
// Iterate and add warning for "additionalProperties"
4444
validateWebStaticConfig.errors?.forEach(error => {
@@ -58,6 +58,7 @@ command
5858
}
5959
} catch (error: any) {
6060
ctx.log.error(`Invalid Web Static Config; ${error.message}`);
61+
process.exitCode = 1;
6162
return;
6263
}
6364
//Print Config here in debug mode
@@ -88,6 +89,7 @@ command
8889
} catch (error) {
8990
console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
9091
process.exitCode = 1;
92+
throw new Error();
9193
}
9294

9395
})

src/commander/commander.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import startServer from './server.js';
99
import stopServer from './stopServer.js'
1010
import ping from './ping.js'
1111
import merge from './merge.js'
12+
import pingTest from './pingTest.js'
1213

1314
const program = new Command();
1415

@@ -36,6 +37,7 @@ program
3637
.addCommand(configAppFigma)
3738
.addCommand(uploadWebFigmaCommand)
3839
.addCommand(uploadAppFigmaCommand)
40+
.addCommand(pingTest)
3941

4042

4143

src/commander/ping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ command
3434
console.error(chalk.red('Error: SmartUI server did not respond in 15 seconds'));
3535
} else {
3636
console.error(chalk.red('SmartUI server is not running'));
37+
console.error(chalk.red(`Error: ${error?.code}`));
38+
console.error(error);
3739
}
3840
}
3941
});

src/commander/pingTest.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Command } from 'commander';
2+
import * as http from 'http';
3+
import * as https from 'https';
4+
import chalk from 'chalk'
5+
6+
function getSmartUIServerAddress() {
7+
const serverAddress = process.env.SMARTUI_SERVER_ADDRESS || 'http://localhost:49152';
8+
return serverAddress;
9+
}
10+
11+
function makeHttpRequest(url: string, timeout: number): Promise<{ status: number; data: any }> {
12+
return new Promise((resolve, reject) => {
13+
const urlObj = new URL(url);
14+
const isHttps = urlObj.protocol === 'https:';
15+
const client = isHttps ? https : http;
16+
17+
const req = client.request(url, { timeout }, (res) => {
18+
let data = '';
19+
20+
res.on('data', (chunk) => {
21+
data += chunk;
22+
});
23+
24+
res.on('end', () => {
25+
let parsedData;
26+
try {
27+
parsedData = JSON.parse(data);
28+
} catch {
29+
parsedData = data;
30+
}
31+
32+
resolve({
33+
status: res.statusCode || 0,
34+
data: parsedData
35+
});
36+
});
37+
});
38+
39+
req.on('error', (error) => {
40+
console.error(error)
41+
reject(error);
42+
});
43+
44+
req.on('timeout', () => {
45+
req.destroy();
46+
const timeoutError = new Error('Request timeout');
47+
(timeoutError as any).code = 'ECONNABORTED';
48+
reject(timeoutError);
49+
});
50+
51+
req.end();
52+
});
53+
}
54+
55+
const command = new Command();
56+
57+
command
58+
.name('exec:pingTest')
59+
.description('Ping the SmartUI server to check if it is running using default http client')
60+
.action(async function(this: Command) {
61+
try {
62+
console.log(chalk.yellow("Pinging server using default http client..."));
63+
const serverAddress = getSmartUIServerAddress();
64+
console.log(chalk.yellow(`Pinging server at ${serverAddress} from terminal using default http client...`));
65+
66+
// Send GET request to the /ping endpoint
67+
const response = await makeHttpRequest(`${serverAddress}/ping`, 15000);
68+
69+
// Log the response from the server
70+
if (response.status === 200) {
71+
console.log(chalk.green('SmartUI Server is running'));
72+
console.log(chalk.green(`Response: ${JSON.stringify(response.data)}`)); // Log response data if needed
73+
} else {
74+
console.log(chalk.red('Failed to reach the server'));
75+
}
76+
} catch (error: any) {
77+
// Handle any errors during the HTTP request
78+
if (error.code === 'ECONNABORTED') {
79+
console.error(chalk.red('Error: SmartUI server did not respond in 15 seconds'));
80+
} else {
81+
console.error(chalk.red('SmartUI server is not running'));
82+
console.error(chalk.red(`Error: ${error?.code}`));
83+
console.error(error);
84+
}
85+
}
86+
});
87+
88+
export default command;

src/lib/ctx.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default (options: Record<string, string>): Context => {
2424
let fetchResultsFileObj: string;
2525
let buildNameObj: string;
2626
let allowDuplicateSnapshotNames: boolean = false;
27+
let useLambdaInternal: boolean = false;
2728
try {
2829
if (options.config) {
2930
config = JSON.parse(fs.readFileSync(options.config, 'utf-8'));
@@ -73,7 +74,7 @@ export default (options: Record<string, string>): Context => {
7374
}
7475
} catch (error: any) {
7576
console.log(`[smartui] Error: ${error.message}`);
76-
process.exit();
77+
process.exit(1);
7778
}
7879

7980
if (config.web) {
@@ -96,6 +97,9 @@ export default (options: Record<string, string>): Context => {
9697
if (config.allowDuplicateSnapshotNames) {
9798
allowDuplicateSnapshotNames = true;
9899
}
100+
if (config.useLambdaInternal) {
101+
useLambdaInternal = true;
102+
}
99103

100104
return {
101105
env: env,
@@ -122,6 +126,7 @@ export default (options: Record<string, string>): Context => {
122126
userAgent: config.userAgent || '',
123127
requestHeaders: config.requestHeaders || {},
124128
allowDuplicateSnapshotNames: allowDuplicateSnapshotNames,
129+
useLambdaInternal: useLambdaInternal,
125130
},
126131
uploadFilePath: '',
127132
webStaticConfig: [],

src/lib/httpClient.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ export default class httpClient {
9292

9393
async request(config: AxiosRequestConfig, log: Logger): Promise<Record<string, any>> {
9494
log.debug(`http request: ${config.method} ${config.url}`);
95-
if (config && config.data && !config.data.name && !config.data.snapshot) {
95+
if (config && config.data && !config.data.skipLogging && !config.data.name && !config.data.snapshot) {
9696
log.debug(config.data);
9797
}
98-
if (config && config.data && config.data.snapshotUuid) {
98+
if (config && config.data && !config.data.skipLogging && config.data.snapshotUuid) {
9999
log.debug(config.data);
100100
}
101101
return this.axiosInstance.request(config)
@@ -497,6 +497,38 @@ export default class httpClient {
497497
}, ctx.log)
498498
}
499499

500+
sendDomToLSRS(ctx: Context, snapshot: ProcessedSnapshot, snapshotUuid: string) {
501+
return this.request({
502+
url: `/upload/dom`,
503+
method: 'POST',
504+
data: {
505+
buildId: ctx.build.id,
506+
snapshotName: snapshot.name,
507+
snapshotUuid: snapshotUuid,
508+
domContent: snapshot,
509+
skipLogging: true
510+
}
511+
}, ctx.log);
512+
}
513+
514+
sendDomToLSRSForCaps(ctx: Context, snapshot: ProcessedSnapshot, snapshotUuid: string, capsBuildId: string, capsProjectToken: string) {
515+
return this.request({
516+
url: `/upload/dom`,
517+
method: 'POST',
518+
headers: {
519+
'Content-Type': 'application/json',
520+
projectToken: capsProjectToken !== '' ? capsProjectToken : this.projectToken
521+
},
522+
data: {
523+
buildId: capsBuildId,
524+
snapshotName: snapshot.name,
525+
snapshotUuid: snapshotUuid,
526+
domContent: snapshot,
527+
skipLogging: true
528+
}
529+
}, ctx.log);
530+
}
531+
500532
uploadLogs(ctx: Context, uploadURL: string) {
501533
const fileStream = fs.createReadStream(constants.LOG_FILE_PATH);
502534
const { size } = fs.statSync(constants.LOG_FILE_PATH);
@@ -514,6 +546,20 @@ export default class httpClient {
514546
}, ctx.log)
515547
}
516548

549+
sendCliLogsToLSRS(ctx: Context) {
550+
const logContent = fs.readFileSync(constants.LOG_FILE_PATH, 'utf-8');
551+
552+
return this.request({
553+
url: `/upload/logs`,
554+
method: 'POST',
555+
data: {
556+
buildId: ctx.build.id,
557+
logContent: logContent,
558+
skipLogging: true
559+
}
560+
}, ctx.log);
561+
}
562+
517563
uploadSnapshotToS3(ctx: Context, uploadURL: string, snapshot: Snapshot) {
518564
return this.request({
519565
url: uploadURL,

src/lib/schemaValidation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ const ConfigSchema = {
262262
allowDuplicateSnapshotNames: {
263263
type: "boolean",
264264
errorMessage: "Invalid config; allowDuplicateSnapshotNames must be true/false"
265+
},
266+
useLambdaInternal: {
267+
type: "boolean",
268+
errorMessage: "Invalid config; useLambdaInternal must be true/false"
265269
}
266270
},
267271
anyOf: [

src/lib/snapshotQueue.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import processSnapshot, {prepareSnapshot} from "./processSnapshot.js"
55
import { v4 as uuidv4 } from 'uuid';
66
import { startPolling, stopTunnelHelper, calculateVariantCountFromSnapshot } from "./utils.js";
77

8+
const uploadDomToS3ViaEnv = process.env.USE_LAMBDA_INTERNAL || false;
89
export default class Queue {
910
private snapshots: Array<Snapshot> = [];
1011
private processedSnapshots: Array<Record<string, any>> = [];
@@ -347,10 +348,16 @@ export default class Queue {
347348
this.ctx.log.info(`Using cached buildId: ${capsBuildId}`);
348349
if (useKafkaFlowCaps) {
349350
const snapshotUuid = uuidv4();
350-
const presignedResponse = await this.ctx.client.getS3PresignedURLForSnapshotUploadCaps(this.ctx, processedSnapshot.name, snapshotUuid, capsBuildId, capsProjectToken);
351-
const uploadUrl = presignedResponse.data.url;
352-
353-
await this.ctx.client.uploadSnapshotToS3Caps(this.ctx, uploadUrl, processedSnapshot, capsProjectToken)
351+
let uploadDomToS3 = this.ctx.config.useLambdaInternal || uploadDomToS3ViaEnv;
352+
if (!uploadDomToS3) {
353+
this.ctx.log.debug(`Uploading dom to S3 for snapshot using presigned URL for CAPS`);
354+
const presignedResponse = await this.ctx.client.getS3PresignedURLForSnapshotUploadCaps(this.ctx, processedSnapshot.name, snapshotUuid, capsBuildId, capsProjectToken);
355+
const uploadUrl = presignedResponse.data.url;
356+
await this.ctx.client.uploadSnapshotToS3Caps(this.ctx, uploadUrl, processedSnapshot, capsProjectToken)
357+
} else {
358+
this.ctx.log.debug(`Uploading dom to S3 for snapshot using LSRS`);
359+
await this.ctx.client.sendDomToLSRSForCaps(this.ctx, processedSnapshot, snapshotUuid, capsBuildId, capsProjectToken);
360+
}
354361
await this.ctx.client.processSnapshotCaps(this.ctx, processedSnapshot, snapshotUuid, capsBuildId, capsProjectToken, discoveryErrors);
355362
} else {
356363
await this.ctx.client.uploadSnapshotForCaps(this.ctx, processedSnapshot, capsBuildId, capsProjectToken, discoveryErrors);
@@ -386,15 +393,22 @@ export default class Queue {
386393
}
387394
if (this.ctx.build && this.ctx.build.useKafkaFlow) {
388395
let snapshotUuid = uuidv4();
389-
396+
let snapshotUploadResponse
390397
if (snapshot?.options?.contextId && this.ctx.contextToSnapshotMap?.has(snapshot.options.contextId)) {
391-
snapshotUuid = snapshot.options.contextId;
392-
}
393-
394-
const presignedResponse = await this.ctx.client.getS3PresignedURLForSnapshotUpload(this.ctx, processedSnapshot.name, snapshotUuid);
395-
const uploadUrl = presignedResponse.data.url;
398+
snapshotUuid = snapshot.options.contextId;
399+
}
400+
let uploadDomToS3 = this.ctx.config.useLambdaInternal || uploadDomToS3ViaEnv;
401+
if (!uploadDomToS3) {
402+
this.ctx.log.debug(`Uploading dom to S3 for snapshot using presigned URL`);
403+
const presignedResponse = await this.ctx.client.getS3PresignedURLForSnapshotUpload(this.ctx, processedSnapshot.name, snapshotUuid);
404+
const uploadUrl = presignedResponse.data.url;
405+
snapshotUploadResponse = await this.ctx.client.uploadSnapshotToS3(this.ctx, uploadUrl, processedSnapshot);
406+
} else {
407+
this.ctx.log.debug(`Uploading dom to S3 for snapshot using LSRS`);
408+
snapshotUploadResponse = await this.ctx.client.sendDomToLSRS(this.ctx, processedSnapshot, snapshotUuid);
409+
}
410+
396411

397-
let snapshotUploadResponse = await this.ctx.client.uploadSnapshotToS3(this.ctx, uploadUrl, processedSnapshot);
398412
if (!snapshotUploadResponse || Object.keys(snapshotUploadResponse).length === 0) {
399413
this.ctx.log.debug(`snapshot failed; Unable to upload dom to S3`);
400414
this.processedSnapshots.push({ name: snapshot?.name, error: `snapshot failed; Unable to upload dom to S3` });

0 commit comments

Comments
 (0)