From 479c0107e606da11fc1be524a2ca4e96e6cb4cea Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 17:36:56 +0530 Subject: [PATCH 1/7] Add PDF upload functionality and related commands --- src/commander/commander.ts | 2 + src/commander/uploadPdf.ts | 60 ++++++++++++++ src/lib/env.ts | 2 + src/lib/httpClient.ts | 70 ++++++++++++++++ src/lib/utils.ts | 165 ++++++++++++++++++++++++++++++++++++- src/tasks/uploadPdfs.ts | 72 ++++++++++++++++ src/types.ts | 1 + 7 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 src/commander/uploadPdf.ts create mode 100644 src/tasks/uploadPdfs.ts diff --git a/src/commander/commander.ts b/src/commander/commander.ts index 25f59e5..5daf935 100644 --- a/src/commander/commander.ts +++ b/src/commander/commander.ts @@ -10,6 +10,7 @@ import stopServer from './stopServer.js' import ping from './ping.js' import merge from './merge.js' import pingTest from './pingTest.js' +import uploadPdf from "./uploadPdf.js"; const program = new Command(); @@ -38,6 +39,7 @@ program .addCommand(uploadWebFigmaCommand) .addCommand(uploadAppFigmaCommand) .addCommand(pingTest) + .addCommand(uploadPdf) diff --git a/src/commander/uploadPdf.ts b/src/commander/uploadPdf.ts new file mode 100644 index 0000000..29ceeea --- /dev/null +++ b/src/commander/uploadPdf.ts @@ -0,0 +1,60 @@ +import {Command} from "commander"; +import { Context } from '../types.js'; +import ctxInit from '../lib/ctx.js'; +import { color, Listr, ListrDefaultRendererLogLevels, LoggerFormat } from 'listr2'; +import fs from 'fs'; +import auth from '../tasks/auth.js'; +import uploadPdfs from '../tasks/uploadPdfs.js'; +import {startPdfPolling} from "../lib/utils.js"; +const command = new Command(); + +command + .name('upload-pdf') + .description('Upload PDFs for visual comparison') + .argument('', 'Path of the directory containing PDFs') + .option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., .json') + .option('--buildName ', 'Specify the build name') + .action(async function(directory, _, command) { + const options = command.optsWithGlobals(); + if (options.buildName === '') { + console.log(`Error: The '--buildName' option cannot be an empty string.`); + process.exit(1); + } + let ctx: Context = ctxInit(command.optsWithGlobals()); + + if (!fs.existsSync(directory)) { + console.log(`Error: The provided directory ${directory} not found.`); + return; + } + + ctx.uploadFilePath = directory; + + let tasks = new Listr( + [ + auth(ctx), + uploadPdfs(ctx) + ], + { + rendererOptions: { + icon: { + [ListrDefaultRendererLogLevels.OUTPUT]: `→` + }, + color: { + [ListrDefaultRendererLogLevels.OUTPUT]: color.gray as LoggerFormat + } + } + } + ); + + try { + await tasks.run(ctx); + + if (ctx.options.fetchResults) { + startPdfPolling(ctx); + } + } catch (error) { + console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/'); + } + }); + +export default command; \ No newline at end of file diff --git a/src/lib/env.ts b/src/lib/env.ts index 5dd1bab..8e8cf1d 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -4,6 +4,7 @@ export default (): Env => { const { PROJECT_TOKEN = '', SMARTUI_CLIENT_API_URL = 'https://api.lambdatest.com/visualui/1.0', + SMARTUI_UPLOAD_URL = 'https://api.lambdatest.com', SMARTUI_GIT_INFO_FILEPATH, SMARTUI_DO_NOT_USE_CAPTURED_COOKIES, HTTP_PROXY, @@ -27,6 +28,7 @@ export default (): Env => { return { PROJECT_TOKEN, SMARTUI_CLIENT_API_URL, + SMARTUI_UPLOAD_URL: SMARTUI_UPLOAD_URL, SMARTUI_GIT_INFO_FILEPATH, HTTP_PROXY, HTTPS_PROXY, diff --git a/src/lib/httpClient.ts b/src/lib/httpClient.ts index acf5562..7b1cf41 100644 --- a/src/lib/httpClient.ts +++ b/src/lib/httpClient.ts @@ -644,4 +644,74 @@ export default class httpClient { } }, ctx.log); } + + async uploadPdf(ctx: Context, form: FormData, log: Logger, buildName?: string): Promise { + form.append('projectToken', this.projectToken); + if (ctx.build.name !== undefined && ctx.build.name !== '') { + form.append('buildName', buildName); + } + ctx.log.debug('Uploading PDF to SmartUI...'); + + const response = await this.axiosInstance.request({ + url: ctx.env.SMARTUI_UPLOAD_URL + '/pdf/upload', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: form, + }); + + log.debug(`http response: ${JSON.stringify({ + status: response.status, + headers: response.headers, + body: response.data + })}`); + + return response.data; + } + + async fetchPdfResults(ctx: Context, log: Logger): Promise { + const params: Record = { + projectToken: this.projectToken + }; + + // Use buildId if available, otherwise use buildName + if (ctx.build.id) { + params.buildId = ctx.build.id; + } else if (ctx.build.name) { + params.buildName = ctx.build.name; + } + + const auth = Buffer.from(`${this.username}:${this.accessKey}`).toString('base64'); + + try { + const response = await axios.request({ + url: ctx.env.SMARTUI_UPLOAD_URL + '/automation/smart-ui/screenshot/build/status', + method: 'GET', + params: params, + headers: { + 'accept': 'application/json', + 'Authorization': `Basic ${auth}` + } + }); + + log.debug(`http response: ${JSON.stringify({ + status: response.status, + headers: response.headers, + body: response.data + })}`); + + return response.data; + } catch (error: any) { + log.error(`Error fetching PDF results: ${error.message}`); + if (error.response) { + log.debug(`Response error: ${JSON.stringify({ + status: error.response.status, + data: error.response.data + })}`); + } + throw error; + } + } } + diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 21384e1..659f37c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -509,4 +509,167 @@ export function calculateVariantCountFromSnapshot(snapshot: any, globalConfig?: } return variantCount; -} \ No newline at end of file +} + +export function startPdfPolling(ctx: Context) { + console.log(chalk.yellow('\nFetching PDF test results...')); + + if (!ctx.build.id && !ctx.build.name) { + ctx.log.error(chalk.red('Error: Build information not found for fetching results')); + return + } + + if (!ctx.env.LT_USERNAME || !ctx.env.LT_ACCESS_KEY) { + console.log(chalk.red('Error: LT_USERNAME and LT_ACCESS_KEY environment variables are required for fetching results')); + return; + } + + let attempts = 0; + const maxAttempts = 30; // 5 minutes (10 seconds * 30) + + console.log(chalk.yellow('Waiting for results...')); + + const interval = setInterval(async () => { + attempts++; + + try { + const response = await ctx.client.fetchPdfResults(ctx, ctx.log); + + if (response.status === 'success' && response.data && response.data.Screenshots) { + clearInterval(interval); + + const pdfGroups = groupScreenshotsByPdf(response.data.Screenshots); + + const pdfsWithMismatches = countPdfsWithMismatches(pdfGroups); + const pagesWithMismatches = countPagesWithMismatches(response.data.Screenshots); + + console.log(chalk.green('\nāœ“ PDF Test Results:')); + console.log(chalk.green(`Build Name: ${response.data.buildName}`)); + console.log(chalk.green(`Project Name: ${response.data.projectName}`)); + console.log(chalk.green(`Total PDFs: ${Object.keys(pdfGroups).length}`)); + console.log(chalk.green(`Total Pages: ${response.data.Screenshots.length}`)); + + if (pdfsWithMismatches > 0 || pagesWithMismatches > 0) { + console.log(chalk.yellow(`${pdfsWithMismatches} PDFs and ${pagesWithMismatches} Pages in build ${response.data.buildName} have changes present.`)); + } else { + console.log(chalk.green('All PDFs match the baseline.')); + } + + Object.entries(pdfGroups).forEach(([pdfName, pages]) => { + const hasMismatch = pages.some(page => page.mismatchPercentage > 0); + const statusColor = hasMismatch ? chalk.yellow : chalk.green; + + console.log(statusColor(`\nšŸ“„ ${pdfName} (${pages.length} pages)`)); + + pages.forEach(page => { + const pageStatusColor = page.mismatchPercentage > 0 ? chalk.yellow : chalk.green; + console.log(pageStatusColor(` - Page ${getPageNumber(page.screenshotName)}: ${page.status} (Mismatch: ${page.mismatchPercentage}%)`)); + }); + }); + + const formattedResults = { + status: response.status, + data: { + buildId: response.data.buildId, + buildName: response.data.buildName, + projectName: response.data.projectName, + buildStatus: response.data.buildStatus, + pdfs: formatPdfsForOutput(pdfGroups) + } + }; + + // Save results to file if filename provided + if (ctx.options.fetchResults && ctx.options.fetchResultsFileName) { + const filename = ctx.options.fetchResultsFileName !== '' ? ctx.options.fetchResultsFileName : 'pdf-results.json'; + + fs.writeFileSync(filename, JSON.stringify(formattedResults, null, 2)); + console.log(chalk.green(`\nResults saved to ${filename}`)); + } + + return; + } else if (response.status === 'error') { + clearInterval(interval); + console.log(chalk.red(`\nError fetching results: ${response.message || 'Unknown error'}`)); + return; + } else { + process.stdout.write(chalk.yellow('.')); + } + + if (attempts >= maxAttempts) { + clearInterval(interval); + console.log(chalk.red('\nTimeout: Could not fetch PDF results after 5 minutes')); + return; + } + + } catch (error: any) { + ctx.log.debug(`Error during polling: ${error.message}`); + + if (attempts >= maxAttempts) { + clearInterval(interval); + console.log(chalk.red('\nTimeout: Could not fetch PDF results after 5 minutes')); + if (error.response && error.response.data) { + console.log(chalk.red(`Error details: ${JSON.stringify(error.response.data)}`)); + } else { + console.log(chalk.red(`Error details: ${error.message}`)); + } + return; + } + process.stdout.write(chalk.yellow('.')); + } + }, 10000); +} + +function groupScreenshotsByPdf(screenshots: any[]): Record { + const pdfGroups: Record = {}; + + screenshots.forEach(screenshot => { + // screenshot name format: "pdf-name.pdf#page-number" + const pdfName = screenshot.screenshotName.split('#')[0]; + + if (!pdfGroups[pdfName]) { + pdfGroups[pdfName] = []; + } + + pdfGroups[pdfName].push(screenshot); + }); + + return pdfGroups; +} + +function countPdfsWithMismatches(pdfGroups: Record): number { + let count = 0; + + Object.values(pdfGroups).forEach(pages => { + if (pages.some(page => page.mismatchPercentage > 0)) { + count++; + } + }); + + return count; +} + +function countPagesWithMismatches(screenshots: any[]): number { + return screenshots.filter(screenshot => screenshot.mismatchPercentage > 0).length; +} + +function formatPdfsForOutput(pdfGroups: Record): any[] { + return Object.entries(pdfGroups).map(([pdfName, pages]) => { + return { + pdfName, + pageCount: pages.length, + pages: pages.map(page => ({ + pageNumber: getPageNumber(page.screenshotName), + screenshotId: page.screenshotId, + mismatchPercentage: page.mismatchPercentage, + threshold: page.threshold, + status: page.status, + screenshotUrl: page.screenshotUrl + })) + }; + }); +} + +function getPageNumber(screenshotName: string): string { + const parts = screenshotName.split('#'); + return parts.length > 1 ? parts[1] : '1'; +} \ No newline at end of file diff --git a/src/tasks/uploadPdfs.ts b/src/tasks/uploadPdfs.ts new file mode 100644 index 0000000..8e40612 --- /dev/null +++ b/src/tasks/uploadPdfs.ts @@ -0,0 +1,72 @@ +import { ListrTask, ListrRendererFactory } from 'listr2'; +import { Context } from '../types.js'; +import chalk from 'chalk'; +import { updateLogContext } from '../lib/logger.js'; +import path from 'path'; +import fs from 'fs'; +import FormData from 'form-data'; + +export default (ctx: Context): ListrTask => { + return { + title: 'Uploading PDFs', + task: async (ctx, task): Promise => { + try { + ctx.task = task; + updateLogContext({ task: 'upload-pdf' }); + + // const pdfs = await getPdfsFromDirectory(ctx.uploadFilePath); + // if (pdfs.length === 0) { + // throw new Error('No PDF files found in the specified directory'); + // } + // + // for (const pdf of pdfs) { + // task.output = `Uploading ${path.basename(pdf)}...`; + // await uploadPdfs(ctx, pdf); + // } + await uploadPdfs(ctx, ctx.uploadFilePath); + + task.title = 'PDFs uploaded successfully'; + } catch (error: any) { + ctx.log.debug(error); + task.output = chalk.gray(`${error.message}`); + throw new Error('Uploading PDFs failed'); + } + }, + rendererOptions: { persistentOutput: true }, + exitOnError: false + }; +}; + +async function getPdfsFromDirectory(directory: string): Promise { + const files = await fs.promises.readdir(directory); + return files + .filter(file => path.extname(file).toLowerCase() === '.pdf') + .map(file => path.join(directory, file)); +} + +async function uploadPdfs(ctx: Context, pdfPath: string): Promise { + const formData = new FormData(); + const files = fs.readdirSync(pdfPath); + const pdfFiles = files.filter(file => file.endsWith('.pdf')); + + pdfFiles.forEach(pdf => { + const filePath = path.join(pdfPath, pdf); + formData.append('pathToFiles', fs.createReadStream(filePath)); + }) + + // formData.append('pathToFiles', fs.createReadStream(pdfPath)); + // formData.append('name', path.basename(pdfPath, '.pdf')); + + const buildName = ctx.options.buildName; + + if (buildName) { + ctx.build.name = buildName; + } + + const response = await ctx.client.uploadPdf(ctx, formData, ctx.log, buildName); + + if (response && response.data && response.data.buildId) { + ctx.build.id = response.data.buildId; + ctx.log.debug(`PDF upload successful. Build ID: ${ctx.build.id}`); + } +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 9637115..5c6eefe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -93,6 +93,7 @@ export interface Context { export interface Env { PROJECT_TOKEN: string; SMARTUI_CLIENT_API_URL: string; + SMARTUI_UPLOAD_URL: string; SMARTUI_DO_NOT_USE_CAPTURED_COOKIES: boolean; SMARTUI_GIT_INFO_FILEPATH: string | undefined; HTTP_PROXY: string | undefined; From 1258ca9d9881e511041ccfb08da40e3f30240ce2 Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 18:11:48 +0530 Subject: [PATCH 2/7] Refactor PDF upload logic to use FormData headers and clean up unused code --- src/lib/httpClient.ts | 4 +--- src/lib/utils.ts | 1 + src/tasks/uploadPdfs.ts | 23 ++--------------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/lib/httpClient.ts b/src/lib/httpClient.ts index 7b1cf41..de6e554 100644 --- a/src/lib/httpClient.ts +++ b/src/lib/httpClient.ts @@ -655,9 +655,7 @@ export default class httpClient { const response = await this.axiosInstance.request({ url: ctx.env.SMARTUI_UPLOAD_URL + '/pdf/upload', method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: form.getHeaders(), data: form, }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 659f37c..b9df591 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -514,6 +514,7 @@ export function calculateVariantCountFromSnapshot(snapshot: any, globalConfig?: export function startPdfPolling(ctx: Context) { console.log(chalk.yellow('\nFetching PDF test results...')); + ctx.log.debug(`Starting fetching results for build: ${ctx.build.id || ctx.build.name}`); if (!ctx.build.id && !ctx.build.name) { ctx.log.error(chalk.red('Error: Build information not found for fetching results')); return diff --git a/src/tasks/uploadPdfs.ts b/src/tasks/uploadPdfs.ts index 8e40612..3f10753 100644 --- a/src/tasks/uploadPdfs.ts +++ b/src/tasks/uploadPdfs.ts @@ -14,15 +14,6 @@ export default (ctx: Context): ListrTask { - const files = await fs.promises.readdir(directory); - return files - .filter(file => path.extname(file).toLowerCase() === '.pdf') - .map(file => path.join(directory, file)); -} - async function uploadPdfs(ctx: Context, pdfPath: string): Promise { const formData = new FormData(); const files = fs.readdirSync(pdfPath); @@ -54,9 +38,6 @@ async function uploadPdfs(ctx: Context, pdfPath: string): Promise { formData.append('pathToFiles', fs.createReadStream(filePath)); }) - // formData.append('pathToFiles', fs.createReadStream(pdfPath)); - // formData.append('name', path.basename(pdfPath, '.pdf')); - const buildName = ctx.options.buildName; if (buildName) { @@ -65,8 +46,8 @@ async function uploadPdfs(ctx: Context, pdfPath: string): Promise { const response = await ctx.client.uploadPdf(ctx, formData, ctx.log, buildName); - if (response && response.data && response.data.buildId) { - ctx.build.id = response.data.buildId; + if (response && response.buildId) { + ctx.build.id = response.buildId; ctx.log.debug(`PDF upload successful. Build ID: ${ctx.build.id}`); } } \ No newline at end of file From 2eec99f541df614d5002e4af03ad98fda4bb615f Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 19:07:54 +0530 Subject: [PATCH 3/7] Enhance PDF upload function to handle single PDF files and improve file reading logic --- src/tasks/uploadPdfs.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/tasks/uploadPdfs.ts b/src/tasks/uploadPdfs.ts index 3f10753..39b552b 100644 --- a/src/tasks/uploadPdfs.ts +++ b/src/tasks/uploadPdfs.ts @@ -30,13 +30,18 @@ export default (ctx: Context): ListrTask { const formData = new FormData(); - const files = fs.readdirSync(pdfPath); - const pdfFiles = files.filter(file => file.endsWith('.pdf')); - pdfFiles.forEach(pdf => { - const filePath = path.join(pdfPath, pdf); - formData.append('pathToFiles', fs.createReadStream(filePath)); - }) + if (pdfPath.endsWith('.pdf')) { + formData.append('pathToFiles', fs.createReadStream(pdfPath)); + } else { + const files = fs.readdirSync(pdfPath); + const pdfFiles = files.filter(file => file.endsWith('.pdf')); + + pdfFiles.forEach(pdf => { + const filePath = path.join(pdfPath, pdf); + formData.append('pathToFiles', fs.createReadStream(filePath)); + }) + } const buildName = ctx.options.buildName; From 2ff20b75fce53159f621c79c4f407524052a6471 Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 20:08:09 +0530 Subject: [PATCH 4/7] Refactor PDF upload methods to improve error handling and logging --- src/lib/httpClient.ts | 58 ++++++++++++++++++++++++----------------- src/tasks/uploadPdfs.ts | 17 +++++++----- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/lib/httpClient.ts b/src/lib/httpClient.ts index de6e554..030fa7b 100644 --- a/src/lib/httpClient.ts +++ b/src/lib/httpClient.ts @@ -14,6 +14,18 @@ export default class httpClient { username: string; accessKey: string; + private handleHttpError(error: any, log: Logger): never { + if (error.response) { + log.debug(`http response error: ${JSON.stringify({ + status: error.response.status, + body: error.response.data + })}`); + throw new Error(error.response.data?.message || error.response.data || `HTTP ${error.response.status} error`); + } + log.debug(`http request failed: ${error.message}`); + throw new Error(error.message); + } + constructor({ SMARTUI_CLIENT_API_URL, PROJECT_TOKEN, PROJECT_NAME, LT_USERNAME, LT_ACCESS_KEY, SMARTUI_API_PROXY, SMARTUI_API_SKIP_CERTIFICATES }: Env) { this.projectToken = PROJECT_TOKEN || ''; this.projectName = PROJECT_NAME || ''; @@ -83,6 +95,8 @@ export default class httpClient { // If we've reached max retries, reject with the error return Promise.reject(error); + } else { + return Promise.reject(error); } } ); @@ -645,30 +659,33 @@ export default class httpClient { }, ctx.log); } - async uploadPdf(ctx: Context, form: FormData, log: Logger, buildName?: string): Promise { + async uploadPdf(ctx: Context, form: FormData, buildName?: string): Promise { form.append('projectToken', this.projectToken); if (ctx.build.name !== undefined && ctx.build.name !== '') { form.append('buildName', buildName); } - ctx.log.debug('Uploading PDF to SmartUI...'); - const response = await this.axiosInstance.request({ - url: ctx.env.SMARTUI_UPLOAD_URL + '/pdf/upload', - method: 'POST', - headers: form.getHeaders(), - data: form, - }); + try { + const response = await this.axiosInstance.request({ + url: ctx.env.SMARTUI_UPLOAD_URL + '/pdf/upload', + method: 'POST', + headers: form.getHeaders(), + data: form, + }); - log.debug(`http response: ${JSON.stringify({ - status: response.status, - headers: response.headers, - body: response.data - })}`); + ctx.log.debug(`http response: ${JSON.stringify({ + status: response.status, + headers: response.headers, + body: response.data + })}`); - return response.data; + return response.data; + } catch (error: any) { + this.handleHttpError(error, ctx.log); + } } - async fetchPdfResults(ctx: Context, log: Logger): Promise { + async fetchPdfResults(ctx: Context): Promise { const params: Record = { projectToken: this.projectToken }; @@ -693,7 +710,7 @@ export default class httpClient { } }); - log.debug(`http response: ${JSON.stringify({ + ctx.log.debug(`http response: ${JSON.stringify({ status: response.status, headers: response.headers, body: response.data @@ -701,14 +718,7 @@ export default class httpClient { return response.data; } catch (error: any) { - log.error(`Error fetching PDF results: ${error.message}`); - if (error.response) { - log.debug(`Response error: ${JSON.stringify({ - status: error.response.status, - data: error.response.data - })}`); - } - throw error; + this.handleHttpError(error, ctx.log); } } } diff --git a/src/tasks/uploadPdfs.ts b/src/tasks/uploadPdfs.ts index 39b552b..ddc651d 100644 --- a/src/tasks/uploadPdfs.ts +++ b/src/tasks/uploadPdfs.ts @@ -19,8 +19,8 @@ export default (ctx: Context): ListrTask { ctx.build.name = buildName; } - const response = await ctx.client.uploadPdf(ctx, formData, ctx.log, buildName); - - if (response && response.buildId) { - ctx.build.id = response.buildId; - ctx.log.debug(`PDF upload successful. Build ID: ${ctx.build.id}`); + try { + const response = await ctx.client.uploadPdf(ctx, formData, buildName); + if (response && response.buildId) { + ctx.build.id = response.buildId; + ctx.log.debug(`PDF upload successful. Build ID: ${ctx.build.id}`); + } + } catch (error : any) { + throw new Error(error.message); } } \ No newline at end of file From 3cf9ad0054abb9017770c44e89d156e71c590a41 Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 20:13:40 +0530 Subject: [PATCH 5/7] Update error handling in PDF upload to exit process on directory not found --- src/commander/uploadPdf.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commander/uploadPdf.ts b/src/commander/uploadPdf.ts index 29ceeea..a82246c 100644 --- a/src/commander/uploadPdf.ts +++ b/src/commander/uploadPdf.ts @@ -24,7 +24,7 @@ command if (!fs.existsSync(directory)) { console.log(`Error: The provided directory ${directory} not found.`); - return; + process.exit(1); } ctx.uploadFilePath = directory; From 0c7856602363ab1ee7b5444688915dd44e36b028 Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 20:14:40 +0530 Subject: [PATCH 6/7] Changes --- src/commander/uploadPdf.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commander/uploadPdf.ts b/src/commander/uploadPdf.ts index a82246c..07c319f 100644 --- a/src/commander/uploadPdf.ts +++ b/src/commander/uploadPdf.ts @@ -54,6 +54,7 @@ command } } catch (error) { console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/'); + process.exit(1); } }); From 3cfd4a6574f1939573ba8db4bc218ed2fec9a6df Mon Sep 17 00:00:00 2001 From: Zeeshan Date: Mon, 8 Sep 2025 20:15:47 +0530 Subject: [PATCH 7/7] Fix http client --- src/lib/httpClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/httpClient.ts b/src/lib/httpClient.ts index 030fa7b..3cbd31f 100644 --- a/src/lib/httpClient.ts +++ b/src/lib/httpClient.ts @@ -15,7 +15,7 @@ export default class httpClient { accessKey: string; private handleHttpError(error: any, log: Logger): never { - if (error.response) { + if (error && error.response) { log.debug(`http response error: ${JSON.stringify({ status: error.response.status, body: error.response.data