From a9897442a37c831da3d08bce02a03a8bcfe8c061 Mon Sep 17 00:00:00 2001
From: parthkirsan <parthkirsan@lambdatest.com>
Date: Wed, 3 Sep 2025 20:02:36 +0530
Subject: [PATCH 01/13] initial commit for smartui caps in start server

---
 src/commander/server.ts |  8 ++++----
 src/lib/server.ts       | 45 ++++++++++++++++++++++++++++++-----------
 2 files changed, 37 insertions(+), 16 deletions(-)

diff --git a/src/commander/server.ts b/src/commander/server.ts
index 1fbea907..11ac2675 100644
--- a/src/commander/server.ts
+++ b/src/commander/server.ts
@@ -2,10 +2,10 @@ import { Command } from 'commander';
 import { Context } from '../types.js';
 import { color, Listr, ListrDefaultRendererLogLevels } from 'listr2';
 import startServer from '../tasks/startServer.js';
-import auth from '../tasks/auth.js';
+import authExec from '../tasks/authExec.js';
 import ctxInit from '../lib/ctx.js';
 import getGitInfo from '../tasks/getGitInfo.js';
-import createBuild from '../tasks/createBuild.js';
+import createBuildExec from '../tasks/createBuildExec.js';
 import snapshotQueue from '../lib/snapshotQueue.js';
 import { startPolling, startPingPolling } from '../lib/utils.js';
 
@@ -30,10 +30,10 @@ command
 
         let tasks = new Listr<Context>(
             [
-                auth(ctx),
+                authExec(ctx),
                 startServer(ctx),
                 getGitInfo(ctx),
-                createBuild(ctx),
+                createBuildExec(ctx),
 
             ],
             {
diff --git a/src/lib/server.ts b/src/lib/server.ts
index e2a1955a..fd363b4a 100644
--- a/src/lib/server.ts
+++ b/src/lib/server.ts
@@ -118,23 +118,44 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
 					}
 				}, 1000);
 			})
-			await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
+
+			for (const [sessionId, capabilities] of ctx.sessionCapabilitiesMap.entries()) {
+                try {
+                    const buildId = capabilities?.buildId || '';
+                    const projectToken = capabilities?.projectToken || '';
+                    const totalSnapshots = capabilities?.snapshotCount || 0;
+                    const sessionBuildUrl = capabilities?.buildURL || '';
+                    const testId = capabilities?.id || '';
+
+                    if (buildId && projectToken) {
+                        await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
+                    }
+
+                    if (testId && buildId) {
+                        buildUrls += `TestId ${testId}: ${sessionBuildUrl}\n`;
+                    }
+                } catch (error: any) {
+                    ctx.log.debug(`Error finalizing build for session ${sessionId}: ${error.message}`);
+                }
+            }
+
+			if (ctx.build && ctx.build.id) {
+				await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
+				let uploadCLILogsToS3 = ctx?.config?.useLambdaInternal || uploadDomToS3ViaEnv;
+				if (!uploadCLILogsToS3) {
+					ctx.log.debug(`Log file to be uploaded`)
+					let resp = await ctx.client.getS3PreSignedURL(ctx);
+					await ctx.client.uploadLogs(ctx, resp.data.url);
+				} else {
+					ctx.log.debug(`Skipping upload of CLI logs as useLambdaInternal is set`)
+				}
+			}
+
 			await ctx.browser?.close();
 			if (ctx.server){
 				ctx.server.close();
 			}
 
-			let uploadCLILogsToS3 = ctx?.config?.useLambdaInternal || uploadDomToS3ViaEnv;
-			if (!uploadCLILogsToS3) {
-				ctx.log.debug(`Log file to be uploaded`)
-				let resp = await ctx.client.getS3PreSignedURL(ctx);
-				await ctx.client.uploadLogs(ctx, resp.data.url);
-			} else {
-				ctx.log.debug(`Skipping upload of CLI logs as useLambdaInternal is set`)
-				// ctx.log.debug(`Log file to be uploaded via LSRS`)
-				// let resp = ctx.client.sendCliLogsToLSRS(ctx);
-			}
-
 			if (pingIntervalId !== null) {
 				clearInterval(pingIntervalId);
 				ctx.log.debug('Ping polling stopped immediately.');

From 479c0107e606da11fc1be524a2ca4e96e6cb4cea Mon Sep 17 00:00:00 2001
From: Zeeshan <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 17:36:56 +0530
Subject: [PATCH 02/13] 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 25f59e5b..5daf9357 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 00000000..29ceeea0
--- /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('<directory>', 'Path of the directory containing PDFs')
+    .option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
+    .option('--buildName <string>', '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<Context>(
+            [
+                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 5dd1bab1..8e8cf1d5 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 acf55626..7b1cf41c 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<any> {
+        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<any> {
+        const params: Record<string, string> = {
+            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 21384e15..659f37c0 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<string, any[]> {
+    const pdfGroups: Record<string, any[]> = {};
+
+    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<string, any[]>): 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<string, any[]>): 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 00000000..8e406129
--- /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<Context, ListrRendererFactory, ListrRendererFactory> => {
+    return {
+        title: 'Uploading PDFs',
+        task: async (ctx, task): Promise<void> => {
+            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<string[]> {
+    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<void> {
+    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 96371158..5c6eefe4 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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 18:11:48 +0530
Subject: [PATCH 03/13] 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 7b1cf41c..de6e554a 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 659f37c0..b9df5919 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 8e406129..3f107536 100644
--- a/src/tasks/uploadPdfs.ts
+++ b/src/tasks/uploadPdfs.ts
@@ -14,15 +14,6 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
                 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';
@@ -37,13 +28,6 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
     };
 };
 
-async function getPdfsFromDirectory(directory: string): Promise<string[]> {
-    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<void> {
     const formData = new FormData();
     const files = fs.readdirSync(pdfPath);
@@ -54,9 +38,6 @@ async function uploadPdfs(ctx: Context, pdfPath: string): Promise<void> {
         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<void> {
 
     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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 19:07:54 +0530
Subject: [PATCH 04/13] 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 3f107536..39b552ba 100644
--- a/src/tasks/uploadPdfs.ts
+++ b/src/tasks/uploadPdfs.ts
@@ -30,13 +30,18 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
 
 async function uploadPdfs(ctx: Context, pdfPath: string): Promise<void> {
     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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 20:08:09 +0530
Subject: [PATCH 05/13] 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 de6e554a..030fa7b3 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<any> {
+    async uploadPdf(ctx: Context, form: FormData, buildName?: string): Promise<any> {
         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<any> {
+    async fetchPdfResults(ctx: Context): Promise<any> {
         const params: Record<string, string> = {
             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 39b552ba..ddc651d3 100644
--- a/src/tasks/uploadPdfs.ts
+++ b/src/tasks/uploadPdfs.ts
@@ -19,8 +19,8 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
                 task.title = 'PDFs uploaded successfully';
             } catch (error: any) {
                 ctx.log.debug(error);
-                task.output = chalk.gray(`${error.message}`);
-                throw new Error('Uploading PDFs failed');
+                task.output = chalk.red(`${error.message}`);
+                throw new Error('PDF upload failed');
             }
         },
         rendererOptions: { persistentOutput: true },
@@ -49,10 +49,13 @@ async function uploadPdfs(ctx: Context, pdfPath: string): Promise<void> {
         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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 20:13:40 +0530
Subject: [PATCH 06/13] 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 29ceeea0..a82246cc 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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 20:14:40 +0530
Subject: [PATCH 07/13] Changes

---
 src/commander/uploadPdf.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/commander/uploadPdf.ts b/src/commander/uploadPdf.ts
index a82246cc..07c319f3 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 <zeeshans@lambdatest.com>
Date: Mon, 8 Sep 2025 20:15:47 +0530
Subject: [PATCH 08/13] 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 030fa7b3..3cbd31fd 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

From 8828eeabe6234d4b60f526d2ad9fd9778c13161e Mon Sep 17 00:00:00 2001
From: Sushobhit Dua <38429226+sushobhit-lt@users.noreply.github.com>
Date: Mon, 8 Sep 2025 23:12:26 +0530
Subject: [PATCH 09/13] fix non git info

---
 src/lib/git.ts          | 25 ++++++++++++++++++++++++-
 src/tasks/getGitInfo.ts |  2 +-
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/lib/git.ts b/src/lib/git.ts
index 324e10b0..fc01ec6e 100644
--- a/src/lib/git.ts
+++ b/src/lib/git.ts
@@ -17,11 +17,12 @@ function executeCommand(command: string): string {
 	}
 }
 
-export function isGitRepo(): boolean {
+export function isGitRepo(ctx: Context): boolean {
 	try {
 		executeCommand('git status')
 		return true
 	} catch (error) {
+		setNonGitInfo(ctx)
 		return false
 	}
 }
@@ -82,3 +83,25 @@ export default (ctx: Context): Git => {
 		};
 	}
 }
+
+
+function setNonGitInfo(ctx: Context) {
+	let branch = ctx.env.CURRENT_BRANCH || 'unknown-branch'
+	if (ctx.options.markBaseline) {
+		ctx.env.BASELINE_BRANCH = branch
+		ctx.env.SMART_GIT = false
+	}
+	let githubURL;
+	if (ctx.options.githubURL && ctx.options.githubURL.startsWith('https://')) {
+		githubURL = ctx.options.githubURL;
+	}
+
+	ctx.git = {
+		branch: branch,
+		commitId: '-',
+		commitAuthor: '-',
+		commitMessage: '-',
+		githubURL: githubURL? githubURL : '',
+		baselineBranch: ctx.options.baselineBranch || ctx.env.BASELINE_BRANCH || ''
+	}
+}	
\ No newline at end of file
diff --git a/src/tasks/getGitInfo.ts b/src/tasks/getGitInfo.ts
index b382fc22..734fc890 100644
--- a/src/tasks/getGitInfo.ts
+++ b/src/tasks/getGitInfo.ts
@@ -7,7 +7,7 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
     return {
         title: `Fetching git repo details`,
         skip: (ctx): string => {
-            return (!isGitRepo() && !ctx.env.SMARTUI_GIT_INFO_FILEPATH) ? '[SKIPPED] Fetching git repo details; not a git repo' : '';
+            return (!isGitRepo(ctx) && !ctx.env.SMARTUI_GIT_INFO_FILEPATH) ? '[SKIPPED] Fetching git repo details; not a git repo' : '';
         },
         task: async (ctx, task): Promise<void> => {
             if (ctx.env.CURRENT_BRANCH && ctx.env.CURRENT_BRANCH.trim() === '') {

From 75904fac7ab613e4473e0464a72de353e2073a01 Mon Sep 17 00:00:00 2001
From: Sushobhit Dua <38429226+sushobhit-lt@users.noreply.github.com>
Date: Tue, 9 Sep 2025 13:10:56 +0530
Subject: [PATCH 10/13] remove console log

---
 src/lib/server.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib/server.ts b/src/lib/server.ts
index e2a1955a..b39673a8 100644
--- a/src/lib/server.ts
+++ b/src/lib/server.ts
@@ -63,7 +63,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
 						}
 					} catch (error: any) {
 						ctx.log.debug(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
-						console.log(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
+						// console.log(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
 					}
 				}
 

From 93f2431ea8f7f58820d1dcac7c9f5f4ca5fcb900 Mon Sep 17 00:00:00 2001
From: Sushobhit Dua <38429226+sushobhit-lt@users.noreply.github.com>
Date: Tue, 9 Sep 2025 13:39:41 +0530
Subject: [PATCH 11/13] fix isStartExec

---
 src/lib/snapshotQueue.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib/snapshotQueue.ts b/src/lib/snapshotQueue.ts
index 7cc9bcb2..0ca45a7d 100644
--- a/src/lib/snapshotQueue.ts
+++ b/src/lib/snapshotQueue.ts
@@ -286,7 +286,7 @@ export default class Queue {
                 let drop = false;
 
 
-                if (this.ctx.isStartExec && !this.ctx.config.tunnel) {
+                if (this.ctx.isStartExec) {
                     this.ctx.log.info(`Processing Snapshot: ${snapshot?.name}`);
                 }
 

From 3037ea6db338db36f3ce8880fc9704b3ad2847e2 Mon Sep 17 00:00:00 2001
From: Sushobhit Dua <38429226+sushobhit-lt@users.noreply.github.com>
Date: Tue, 9 Sep 2025 14:19:01 +0530
Subject: [PATCH 12/13] fix poling issue

---
 package.json            | 2 +-
 src/commander/server.ts | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 9947f5bf..c6dc19d0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lambdatest/smartui-cli",
-  "version": "4.1.28",
+  "version": "4.1.29",
   "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
   "files": [
     "dist/**/*"
diff --git a/src/commander/server.ts b/src/commander/server.ts
index 11ac2675..81781622 100644
--- a/src/commander/server.ts
+++ b/src/commander/server.ts
@@ -50,11 +50,12 @@ command
 
         try {
             await tasks.run(ctx);
-            startPingPolling(ctx);
-            if (ctx.options.fetchResults) {
+            if (ctx.build && ctx.build.id) {
+                startPingPolling(ctx);
+            }
+            if (ctx.options.fetchResults && ctx.build && ctx.build.id) {
                 startPolling(ctx, '', false, '')
             }
-            
     
         } catch (error) {
             console.error('Error during server execution:', error);

From a516bd256c4a4a2b4eeef5ad8f80f14d372ce737 Mon Sep 17 00:00:00 2001
From: Zeeshan <zeeshans@lambdatest.com>
Date: Tue, 9 Sep 2025 14:29:19 +0530
Subject: [PATCH 13/13] Refactor PDF result fetching and enhance error handling
 for project ID

---
 src/lib/httpClient.ts   | 16 ++++++------
 src/lib/utils.ts        | 56 ++++++++++++++++++-----------------------
 src/tasks/uploadPdfs.ts |  3 +++
 src/types.ts            |  1 +
 4 files changed, 35 insertions(+), 41 deletions(-)

diff --git a/src/lib/httpClient.ts b/src/lib/httpClient.ts
index 3cbd31fd..9a1ae937 100644
--- a/src/lib/httpClient.ts
+++ b/src/lib/httpClient.ts
@@ -686,22 +686,20 @@ export default class httpClient {
     }
 
     async fetchPdfResults(ctx: Context): Promise<any> {
-        const params: Record<string, string> = {
-            projectToken: this.projectToken
-        };
+        const params: Record<string, string> = {};
 
-        // 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;
+        if (ctx.build.projectId) {
+            params.project_id = ctx.build.projectId;
+        } else {
+            throw new Error('Project ID not found to fetch PDF results');
         }
+        params.build_id = ctx.build.id;
 
         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',
+                url: ctx.env.SMARTUI_UPLOAD_URL + '/smartui/2.0/build/screenshots',
                 method: 'GET',
                 params: params,
                 headers: {
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index b9df5919..5ef2e313 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -526,7 +526,7 @@ export function startPdfPolling(ctx: Context) {
     }
 
     let attempts = 0;
-    const maxAttempts = 30; // 5 minutes (10 seconds * 30)
+    const maxAttempts = 60; // 5 minutes (10 seconds * 30)
 
     console.log(chalk.yellow('Waiting for results...'));
 
@@ -534,47 +534,46 @@ export function startPdfPolling(ctx: Context) {
         attempts++;
 
         try {
-            const response = await ctx.client.fetchPdfResults(ctx, ctx.log);
+            const response = await ctx.client.fetchPdfResults(ctx);
 
-            if (response.status === 'success' && response.data && response.data.Screenshots) {
+            if (response.screenshots) {
                 clearInterval(interval);
 
-                const pdfGroups = groupScreenshotsByPdf(response.data.Screenshots);
-
+                const pdfGroups = groupScreenshotsByPdf(response.screenshots);
                 const pdfsWithMismatches = countPdfsWithMismatches(pdfGroups);
-                const pagesWithMismatches = countPagesWithMismatches(response.data.Screenshots);
+                const pagesWithMismatches = countPagesWithMismatches(response.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(`Build Name: ${response.build.name}`));
+                console.log(chalk.green(`Project Name: ${response.project.name}`));
                 console.log(chalk.green(`Total PDFs: ${Object.keys(pdfGroups).length}`));
-                console.log(chalk.green(`Total Pages: ${response.data.Screenshots.length}`));
+                console.log(chalk.green(`Total Pages: ${response.screenshots.length}`));
 
                 if (pdfsWithMismatches > 0 || pagesWithMismatches > 0) {
-                    console.log(chalk.yellow(`${pdfsWithMismatches} PDFs and ${pagesWithMismatches} Pages in build ${response.data.buildName} have changes present.`));
+                    console.log(chalk.yellow(`${pdfsWithMismatches} PDFs and ${pagesWithMismatches} Pages in build ${response.build.name} 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 hasMismatch = pages.some(page => page.mismatch_percentage > 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 pageStatusColor = page.mismatch_percentage > 0 ? chalk.yellow : chalk.green;
+                        console.log(pageStatusColor(`  - Page ${getPageNumber(page.screenshot_name)}: ${page.status} (Mismatch: ${page.mismatch_percentage}%)`));
                     });
                 });
 
                 const formattedResults = {
-                    status: response.status,
+                    status: 'success',
                     data: {
-                        buildId: response.data.buildId,
-                        buildName: response.data.buildName,
-                        projectName: response.data.projectName,
-                        buildStatus: response.data.buildStatus,
+                        buildId: response.build.id,
+                        buildName: response.build.name,
+                        projectName: response.project.name,
+                        buildStatus: response.build.build_satus,
                         pdfs: formatPdfsForOutput(pdfGroups)
                     }
                 };
@@ -588,12 +587,6 @@ export function startPdfPolling(ctx: Context) {
                 }
 
                 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) {
@@ -625,7 +618,7 @@ function groupScreenshotsByPdf(screenshots: any[]): Record<string, any[]> {
 
     screenshots.forEach(screenshot => {
         // screenshot name format: "pdf-name.pdf#page-number"
-        const pdfName = screenshot.screenshotName.split('#')[0];
+        const pdfName = screenshot.screenshot_name.split('#')[0];
 
         if (!pdfGroups[pdfName]) {
             pdfGroups[pdfName] = [];
@@ -641,7 +634,7 @@ function countPdfsWithMismatches(pdfGroups: Record<string, any[]>): number {
     let count = 0;
 
     Object.values(pdfGroups).forEach(pages => {
-        if (pages.some(page => page.mismatchPercentage > 0)) {
+        if (pages.some(page => page.mismatch_percentage > 0)) {
             count++;
         }
     });
@@ -650,7 +643,7 @@ function countPdfsWithMismatches(pdfGroups: Record<string, any[]>): number {
 }
 
 function countPagesWithMismatches(screenshots: any[]): number {
-    return screenshots.filter(screenshot => screenshot.mismatchPercentage > 0).length;
+    return screenshots.filter(screenshot => screenshot.mismatch_percentage > 0).length;
 }
 
 function formatPdfsForOutput(pdfGroups: Record<string, any[]>): any[] {
@@ -659,12 +652,11 @@ function formatPdfsForOutput(pdfGroups: Record<string, any[]>): any[] {
             pdfName,
             pageCount: pages.length,
             pages: pages.map(page => ({
-                pageNumber: getPageNumber(page.screenshotName),
-                screenshotId: page.screenshotId,
-                mismatchPercentage: page.mismatchPercentage,
-                threshold: page.threshold,
+                pageNumber: getPageNumber(page.screenshot_name),
+                screenshotId: page.captured_image_id,
+                mismatchPercentage: page.mismatch_percentage,
                 status: page.status,
-                screenshotUrl: page.screenshotUrl
+                screenshotUrl: page.shareable_link
             }))
         };
     });
diff --git a/src/tasks/uploadPdfs.ts b/src/tasks/uploadPdfs.ts
index ddc651d3..c790b985 100644
--- a/src/tasks/uploadPdfs.ts
+++ b/src/tasks/uploadPdfs.ts
@@ -55,6 +55,9 @@ async function uploadPdfs(ctx: Context, pdfPath: string): Promise<void> {
             ctx.build.id = response.buildId;
             ctx.log.debug(`PDF upload successful. Build ID: ${ctx.build.id}`);
         }
+        if (response && response.projectId) {
+            ctx.build.projectId = response.projectId;
+        }
     } catch (error : any) {
         throw new Error(error.message);
     }
diff --git a/src/types.ts b/src/types.ts
index 5c6eefe4..0e34e6c0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -181,6 +181,7 @@ export interface Build {
     baseline: boolean;
     useKafkaFlow: boolean;
     hasDiscoveryError: boolean;
+    projectId?: string;
 }
 
 export interface WebConfig {
