Skip to content

Commit 992bae1

Browse files
implement: smartui upload
1 parent 290447f commit 992bae1

File tree

6 files changed

+154
-2
lines changed

6 files changed

+154
-2
lines changed

src/commander/commander.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Command } from 'commander'
22
import exec from './exec.js'
33
import { configWeb, configStatic, configFigma} from './config.js'
44
import capture from './capture.js'
5+
import upload from './upload.js'
56
import { version } from '../../package.json'
67
import uploadFigma from './uploadFigma.js'
78

@@ -16,6 +17,7 @@ program
1617
.addCommand(capture)
1718
.addCommand(configWeb)
1819
.addCommand(configStatic)
20+
.addCommand(upload)
1921
.addCommand(configFigma)
2022
.addCommand(uploadFigma)
2123

src/commander/upload.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import fs from 'fs';
2+
import { Command } from 'commander';
3+
import { Context } from '../types.js';
4+
import { color, Listr, ListrDefaultRendererLogLevels } from 'listr2';
5+
import auth from '../tasks/auth.js';
6+
import ctxInit from '../lib/ctx.js';
7+
import getGitInfo from '../tasks/getGitInfo.js';
8+
import createBuild from '../tasks/createBuild.js';
9+
import uploadScreenshots from '../tasks/uploadScreenshots.js';
10+
import finalizeBuild from '../tasks/finalizeBuild.js';
11+
12+
const command = new Command();
13+
14+
command
15+
.name('upload')
16+
.description('Upload screenshots from given directory')
17+
.argument('<directory>', 'Path of the directory')
18+
.option('-R, --ignoreResolutions', 'Ignore resolution')
19+
.action(async function(directory, _, command) {
20+
let ctx: Context = ctxInit(command.optsWithGlobals());
21+
22+
if (!fs.existsSync(directory)) {
23+
console.log(`Error: The provided directory ${directory} not found.`);
24+
return;
25+
}
26+
27+
ctx.uploadFilePath = directory;
28+
29+
let tasks = new Listr<Context>(
30+
[
31+
auth(ctx),
32+
getGitInfo(ctx),
33+
createBuild(ctx),
34+
uploadScreenshots(ctx),
35+
finalizeBuild(ctx)
36+
],
37+
{
38+
rendererOptions: {
39+
icon: {
40+
[ListrDefaultRendererLogLevels.OUTPUT]: `→`
41+
},
42+
color: {
43+
[ListrDefaultRendererLogLevels.OUTPUT]: color.gray
44+
}
45+
}
46+
}
47+
);
48+
49+
try {
50+
await tasks.run(ctx);
51+
} catch (error) {
52+
console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
53+
}
54+
55+
});
56+
57+
export default command;

src/lib/ctx.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default (options: Record<string, string>): Context => {
1313
let mobileConfig: MobileConfig;
1414
let config = constants.DEFAULT_CONFIG;
1515
let port: number;
16+
let resolutionOff: boolean;
1617

1718
try {
1819
if (options.config) {
@@ -33,6 +34,7 @@ export default (options: Record<string, string>): Context => {
3334
if (isNaN(port) || port < 1 || port > 65535) {
3435
throw new Error('Invalid port number. Port number must be an integer between 1 and 65535.');
3536
}
37+
resolutionOff = options.ignoreResolutions || false;
3638
} catch (error: any) {
3739
console.log(`[smartui] Error: ${error.message}`);
3840
process.exit();
@@ -62,6 +64,7 @@ export default (options: Record<string, string>): Context => {
6264
enableJavaScript: config.enableJavaScript || false,
6365
allowedHostnames: config.allowedHostnames || []
6466
},
67+
uploadFilePath: '',
6568
webStaticConfig: [],
6669
git: {
6770
branch: '',
@@ -81,7 +84,8 @@ export default (options: Record<string, string>): Context => {
8184
parallel: options.parallel ? true : false,
8285
markBaseline: options.markBaseline ? true : false,
8386
buildName: options.buildName || '',
84-
port: port
87+
port: port,
88+
ignoreResolutions: resolutionOff
8589
},
8690
cliVersion: version,
8791
totalSnapshots: -1

src/lib/screenshot.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import sizeOf from 'image-size';
14
import { Browser, BrowserContext, Page } from "@playwright/test"
25
import { Context } from "../types.js"
36
import * as utils from "./utils.js"
@@ -133,4 +136,61 @@ export async function captureScreenshots(ctx: Context): Promise<Record<string,a
133136
utils.delDir('screenshots');
134137

135138
return { capturedScreenshots, output };
136-
}
139+
}
140+
141+
function getImageDimensions(filePath: string): { width: number, height: number } | null {
142+
const buffer = fs.readFileSync(filePath);
143+
let width, height;
144+
145+
if (buffer.toString('hex', 0, 2) === 'ffd8') {
146+
// JPEG
147+
let offset = 2;
148+
while (offset < buffer.length) {
149+
const marker = buffer.toString('hex', offset, offset + 2);
150+
offset += 2;
151+
const length = buffer.readUInt16BE(offset);
152+
if (marker === 'ffc0' || marker === 'ffc2') {
153+
height = buffer.readUInt16BE(offset + 3);
154+
width = buffer.readUInt16BE(offset + 5);
155+
return { width, height };
156+
}
157+
offset += length;
158+
}
159+
} else if (buffer.toString('hex', 1, 4) === '504e47') {
160+
// PNG
161+
width = buffer.readUInt32BE(16);
162+
height = buffer.readUInt32BE(20);
163+
return { width, height };
164+
}
165+
166+
return null;
167+
}
168+
169+
export async function uploadScreenshots(ctx: Context): Promise<void> {
170+
let screenshotsDir = ctx.uploadFilePath;
171+
172+
// Read all files in the screenshots directory
173+
const files = fs.readdirSync(screenshotsDir);
174+
175+
for (let file of files) {
176+
let fileExtension = path.extname(file).toLowerCase();
177+
if (fileExtension === '.png' || fileExtension === '.jpeg' || fileExtension === '.jpg') {
178+
let filePath = `${screenshotsDir}/${file}`;
179+
let ssId = path.basename(file, fileExtension);
180+
181+
let viewport = 'default'
182+
183+
if(!ctx.options.ignoreResolutions){
184+
const dimensions = getImageDimensions(filePath);
185+
if (!dimensions) {
186+
throw new Error(`Unable to determine dimensions for image: ${filePath}`);
187+
}
188+
const width = dimensions.width;
189+
const height = dimensions.height;
190+
viewport = `${width}x${height}`;
191+
}
192+
193+
await ctx.client.uploadScreenshot(ctx.build, filePath, ssId, 'default', viewport, ctx.log);
194+
}
195+
}
196+
}

src/tasks/uploadScreenshots.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ListrTask, ListrRendererFactory } from 'listr2';
2+
import { Context } from '../types.js';
3+
import { uploadScreenshots } from '../lib/screenshot.js';
4+
import chalk from 'chalk';
5+
import { updateLogContext } from '../lib/logger.js';
6+
7+
export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRendererFactory> => {
8+
return {
9+
title: 'Uploading screenshots',
10+
task: async (ctx, task): Promise<void> => {
11+
try {
12+
ctx.task = task;
13+
updateLogContext({ task: 'upload' });
14+
15+
await uploadScreenshots(ctx);
16+
17+
task.title = 'Screenshots uploaded successfully';
18+
} catch (error: any) {
19+
ctx.log.debug(error);
20+
task.output = chalk.gray(`${error.message}`);
21+
throw new Error('Uploading screenshots failed');
22+
}
23+
},
24+
rendererOptions: { persistentOutput: true },
25+
exitOnError: false
26+
};
27+
};

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface Context {
2020
enableJavaScript: boolean;
2121
allowedHostnames: Array<string>;
2222
};
23+
uploadFilePath: string;
2324
webStaticConfig: WebStaticConfig;
2425
build: Build;
2526
git: Git;
@@ -31,6 +32,7 @@ export interface Context {
3132
markBaseline?: boolean,
3233
buildName?: string,
3334
port?: number,
35+
ignoreResolutions?: boolean,
3436
}
3537
cliVersion: string;
3638
totalSnapshots: number;

0 commit comments

Comments
 (0)