Skip to content

Commit eac2d02

Browse files
authored
Merge pull request #356 from LambdaTest/stage
Release PR Version: `4.1.29`
2 parents 85c0c7e + 1a66531 commit eac2d02

File tree

13 files changed

+433
-25
lines changed

13 files changed

+433
-25
lines changed

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.28",
3+
"version": "4.1.29",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"

src/commander/commander.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import stopServer from './stopServer.js'
1010
import ping from './ping.js'
1111
import merge from './merge.js'
1212
import pingTest from './pingTest.js'
13+
import uploadPdf from "./uploadPdf.js";
1314

1415
const program = new Command();
1516

@@ -38,6 +39,7 @@ program
3839
.addCommand(uploadWebFigmaCommand)
3940
.addCommand(uploadAppFigmaCommand)
4041
.addCommand(pingTest)
42+
.addCommand(uploadPdf)
4143

4244

4345

src/commander/server.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { Command } from 'commander';
22
import { Context } from '../types.js';
33
import { color, Listr, ListrDefaultRendererLogLevels } from 'listr2';
44
import startServer from '../tasks/startServer.js';
5-
import auth from '../tasks/auth.js';
5+
import authExec from '../tasks/authExec.js';
66
import ctxInit from '../lib/ctx.js';
77
import getGitInfo from '../tasks/getGitInfo.js';
8-
import createBuild from '../tasks/createBuild.js';
8+
import createBuildExec from '../tasks/createBuildExec.js';
99
import snapshotQueue from '../lib/snapshotQueue.js';
1010
import { startPolling, startPingPolling } from '../lib/utils.js';
1111

@@ -30,10 +30,10 @@ command
3030

3131
let tasks = new Listr<Context>(
3232
[
33-
auth(ctx),
33+
authExec(ctx),
3434
startServer(ctx),
3535
getGitInfo(ctx),
36-
createBuild(ctx),
36+
createBuildExec(ctx),
3737

3838
],
3939
{
@@ -50,11 +50,12 @@ command
5050

5151
try {
5252
await tasks.run(ctx);
53-
startPingPolling(ctx);
54-
if (ctx.options.fetchResults) {
53+
if (ctx.build && ctx.build.id) {
54+
startPingPolling(ctx);
55+
}
56+
if (ctx.options.fetchResults && ctx.build && ctx.build.id) {
5557
startPolling(ctx, '', false, '')
5658
}
57-
5859

5960
} catch (error) {
6061
console.error('Error during server execution:', error);

src/commander/uploadPdf.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {Command} from "commander";
2+
import { Context } from '../types.js';
3+
import ctxInit from '../lib/ctx.js';
4+
import { color, Listr, ListrDefaultRendererLogLevels, LoggerFormat } from 'listr2';
5+
import fs from 'fs';
6+
import auth from '../tasks/auth.js';
7+
import uploadPdfs from '../tasks/uploadPdfs.js';
8+
import {startPdfPolling} from "../lib/utils.js";
9+
const command = new Command();
10+
11+
command
12+
.name('upload-pdf')
13+
.description('Upload PDFs for visual comparison')
14+
.argument('<directory>', 'Path of the directory containing PDFs')
15+
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
16+
.option('--buildName <string>', 'Specify the build name')
17+
.action(async function(directory, _, command) {
18+
const options = command.optsWithGlobals();
19+
if (options.buildName === '') {
20+
console.log(`Error: The '--buildName' option cannot be an empty string.`);
21+
process.exit(1);
22+
}
23+
let ctx: Context = ctxInit(command.optsWithGlobals());
24+
25+
if (!fs.existsSync(directory)) {
26+
console.log(`Error: The provided directory ${directory} not found.`);
27+
process.exit(1);
28+
}
29+
30+
ctx.uploadFilePath = directory;
31+
32+
let tasks = new Listr<Context>(
33+
[
34+
auth(ctx),
35+
uploadPdfs(ctx)
36+
],
37+
{
38+
rendererOptions: {
39+
icon: {
40+
[ListrDefaultRendererLogLevels.OUTPUT]: `→`
41+
},
42+
color: {
43+
[ListrDefaultRendererLogLevels.OUTPUT]: color.gray as LoggerFormat
44+
}
45+
}
46+
}
47+
);
48+
49+
try {
50+
await tasks.run(ctx);
51+
52+
if (ctx.options.fetchResults) {
53+
startPdfPolling(ctx);
54+
}
55+
} catch (error) {
56+
console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
57+
process.exit(1);
58+
}
59+
});
60+
61+
export default command;

src/lib/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default (): Env => {
44
const {
55
PROJECT_TOKEN = '',
66
SMARTUI_CLIENT_API_URL = 'https://api.lambdatest.com/visualui/1.0',
7+
SMARTUI_UPLOAD_URL = 'https://api.lambdatest.com',
78
SMARTUI_GIT_INFO_FILEPATH,
89
SMARTUI_DO_NOT_USE_CAPTURED_COOKIES,
910
HTTP_PROXY,
@@ -27,6 +28,7 @@ export default (): Env => {
2728
return {
2829
PROJECT_TOKEN,
2930
SMARTUI_CLIENT_API_URL,
31+
SMARTUI_UPLOAD_URL: SMARTUI_UPLOAD_URL,
3032
SMARTUI_GIT_INFO_FILEPATH,
3133
HTTP_PROXY,
3234
HTTPS_PROXY,

src/lib/git.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ function executeCommand(command: string): string {
1717
}
1818
}
1919

20-
export function isGitRepo(): boolean {
20+
export function isGitRepo(ctx: Context): boolean {
2121
try {
2222
executeCommand('git status')
2323
return true
2424
} catch (error) {
25+
setNonGitInfo(ctx)
2526
return false
2627
}
2728
}
@@ -82,3 +83,25 @@ export default (ctx: Context): Git => {
8283
};
8384
}
8485
}
86+
87+
88+
function setNonGitInfo(ctx: Context) {
89+
let branch = ctx.env.CURRENT_BRANCH || 'unknown-branch'
90+
if (ctx.options.markBaseline) {
91+
ctx.env.BASELINE_BRANCH = branch
92+
ctx.env.SMART_GIT = false
93+
}
94+
let githubURL;
95+
if (ctx.options.githubURL && ctx.options.githubURL.startsWith('https://')) {
96+
githubURL = ctx.options.githubURL;
97+
}
98+
99+
ctx.git = {
100+
branch: branch,
101+
commitId: '-',
102+
commitAuthor: '-',
103+
commitMessage: '-',
104+
githubURL: githubURL? githubURL : '',
105+
baselineBranch: ctx.options.baselineBranch || ctx.env.BASELINE_BRANCH || ''
106+
}
107+
}

src/lib/httpClient.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ export default class httpClient {
1414
username: string;
1515
accessKey: string;
1616

17+
private handleHttpError(error: any, log: Logger): never {
18+
if (error && error.response) {
19+
log.debug(`http response error: ${JSON.stringify({
20+
status: error.response.status,
21+
body: error.response.data
22+
})}`);
23+
throw new Error(error.response.data?.message || error.response.data || `HTTP ${error.response.status} error`);
24+
}
25+
log.debug(`http request failed: ${error.message}`);
26+
throw new Error(error.message);
27+
}
28+
1729
constructor({ SMARTUI_CLIENT_API_URL, PROJECT_TOKEN, PROJECT_NAME, LT_USERNAME, LT_ACCESS_KEY, SMARTUI_API_PROXY, SMARTUI_API_SKIP_CERTIFICATES }: Env) {
1830
this.projectToken = PROJECT_TOKEN || '';
1931
this.projectName = PROJECT_NAME || '';
@@ -83,6 +95,8 @@ export default class httpClient {
8395

8496
// If we've reached max retries, reject with the error
8597
return Promise.reject(error);
98+
} else {
99+
return Promise.reject(error);
86100
}
87101
}
88102
);
@@ -644,4 +658,66 @@ export default class httpClient {
644658
}
645659
}, ctx.log);
646660
}
661+
662+
async uploadPdf(ctx: Context, form: FormData, buildName?: string): Promise<any> {
663+
form.append('projectToken', this.projectToken);
664+
if (ctx.build.name !== undefined && ctx.build.name !== '') {
665+
form.append('buildName', buildName);
666+
}
667+
668+
try {
669+
const response = await this.axiosInstance.request({
670+
url: ctx.env.SMARTUI_UPLOAD_URL + '/pdf/upload',
671+
method: 'POST',
672+
headers: form.getHeaders(),
673+
data: form,
674+
});
675+
676+
ctx.log.debug(`http response: ${JSON.stringify({
677+
status: response.status,
678+
headers: response.headers,
679+
body: response.data
680+
})}`);
681+
682+
return response.data;
683+
} catch (error: any) {
684+
this.handleHttpError(error, ctx.log);
685+
}
686+
}
687+
688+
async fetchPdfResults(ctx: Context): Promise<any> {
689+
const params: Record<string, string> = {};
690+
691+
if (ctx.build.projectId) {
692+
params.project_id = ctx.build.projectId;
693+
} else {
694+
throw new Error('Project ID not found to fetch PDF results');
695+
}
696+
params.build_id = ctx.build.id;
697+
698+
const auth = Buffer.from(`${this.username}:${this.accessKey}`).toString('base64');
699+
700+
try {
701+
const response = await axios.request({
702+
url: ctx.env.SMARTUI_UPLOAD_URL + '/smartui/2.0/build/screenshots',
703+
method: 'GET',
704+
params: params,
705+
headers: {
706+
'accept': 'application/json',
707+
'Authorization': `Basic ${auth}`
708+
}
709+
});
710+
711+
ctx.log.debug(`http response: ${JSON.stringify({
712+
status: response.status,
713+
headers: response.headers,
714+
body: response.data
715+
})}`);
716+
717+
return response.data;
718+
} catch (error: any) {
719+
this.handleHttpError(error, ctx.log);
720+
}
721+
}
647722
}
723+

src/lib/server.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
6363
}
6464
} catch (error: any) {
6565
ctx.log.debug(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
66-
console.log(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
66+
// console.log(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
6767
}
6868
}
6969

@@ -118,23 +118,44 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
118118
}
119119
}, 1000);
120120
})
121-
await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
121+
122+
for (const [sessionId, capabilities] of ctx.sessionCapabilitiesMap.entries()) {
123+
try {
124+
const buildId = capabilities?.buildId || '';
125+
const projectToken = capabilities?.projectToken || '';
126+
const totalSnapshots = capabilities?.snapshotCount || 0;
127+
const sessionBuildUrl = capabilities?.buildURL || '';
128+
const testId = capabilities?.id || '';
129+
130+
if (buildId && projectToken) {
131+
await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
132+
}
133+
134+
if (testId && buildId) {
135+
buildUrls += `TestId ${testId}: ${sessionBuildUrl}\n`;
136+
}
137+
} catch (error: any) {
138+
ctx.log.debug(`Error finalizing build for session ${sessionId}: ${error.message}`);
139+
}
140+
}
141+
142+
if (ctx.build && ctx.build.id) {
143+
await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
144+
let uploadCLILogsToS3 = ctx?.config?.useLambdaInternal || uploadDomToS3ViaEnv;
145+
if (!uploadCLILogsToS3) {
146+
ctx.log.debug(`Log file to be uploaded`)
147+
let resp = await ctx.client.getS3PreSignedURL(ctx);
148+
await ctx.client.uploadLogs(ctx, resp.data.url);
149+
} else {
150+
ctx.log.debug(`Skipping upload of CLI logs as useLambdaInternal is set`)
151+
}
152+
}
153+
122154
await ctx.browser?.close();
123155
if (ctx.server){
124156
ctx.server.close();
125157
}
126158

127-
let uploadCLILogsToS3 = ctx?.config?.useLambdaInternal || uploadDomToS3ViaEnv;
128-
if (!uploadCLILogsToS3) {
129-
ctx.log.debug(`Log file to be uploaded`)
130-
let resp = await ctx.client.getS3PreSignedURL(ctx);
131-
await ctx.client.uploadLogs(ctx, resp.data.url);
132-
} else {
133-
ctx.log.debug(`Skipping upload of CLI logs as useLambdaInternal is set`)
134-
// ctx.log.debug(`Log file to be uploaded via LSRS`)
135-
// let resp = ctx.client.sendCliLogsToLSRS(ctx);
136-
}
137-
138159
if (pingIntervalId !== null) {
139160
clearInterval(pingIntervalId);
140161
ctx.log.debug('Ping polling stopped immediately.');

src/lib/snapshotQueue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export default class Queue {
286286
let drop = false;
287287

288288

289-
if (this.ctx.isStartExec && !this.ctx.config.tunnel) {
289+
if (this.ctx.isStartExec) {
290290
this.ctx.log.info(`Processing Snapshot: ${snapshot?.name}`);
291291
}
292292

0 commit comments

Comments
 (0)