Skip to content

Commit 2255cc4

Browse files
authored
Merge branch 'stage' into DOT-3640
2 parents 828d772 + ae05eb3 commit 2255cc4

21 files changed

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

src/commander/capture.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ command
1919
.option('-C, --parallel [number]', 'Specify the number of instances per browser', parseInt)
2020
.option('-F, --force', 'forcefully apply the specified parallel instances per browser')
2121
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
22+
.option('--buildName <string>', 'Specify the build name')
2223
.action(async function(file, _, command) {
24+
const options = command.optsWithGlobals();
25+
if (options.buildName === '') {
26+
console.log(`Error: The '--buildName' option cannot be an empty string.`);
27+
process.exit(1);
28+
}
2329
let ctx: Context = ctxInit(command.optsWithGlobals());
2430

2531
if (!fs.existsSync(file)) {

src/commander/commander.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Command } from 'commander'
22
import exec from './exec.js'
3-
import { configWeb, configStatic, configFigma} from './config.js'
3+
import { configWeb, configStatic, configFigma, configWebFigma} from './config.js'
44
import capture from './capture.js'
55
import upload from './upload.js'
66
import { version } from '../../package.json'
7-
import uploadFigma from './uploadFigma.js'
7+
import { uploadFigma, uploadWebFigmaCommand } from './uploadFigma.js'
88

99
const program = new Command();
1010

@@ -20,6 +20,9 @@ program
2020
.addCommand(upload)
2121
.addCommand(configFigma)
2222
.addCommand(uploadFigma)
23+
.addCommand(configWebFigma)
24+
.addCommand(uploadWebFigmaCommand)
25+
2326

2427

2528
export default program;

src/commander/config.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Command } from 'commander'
2-
import { createConfig, createWebStaticConfig, createFigmaConfig } from '../lib/config.js'
2+
import { createConfig, createWebStaticConfig, createFigmaConfig, createWebFigmaConfig } from '../lib/config.js'
33

44
export const configWeb = new Command();
55
export const configStatic = new Command();
66
export const configFigma = new Command();
7+
export const configWebFigma = new Command();
8+
79

810
configWeb
911
.name('config:create')
@@ -29,4 +31,11 @@ configFigma
2931
createFigmaConfig(filepath);
3032
})
3133

34+
configWebFigma
35+
.name('config:create-figma-web')
36+
.description('Create figma config file with browsers')
37+
.argument('[filepath]', 'Optional config filepath')
38+
.action(async function(filepath, options) {
39+
createWebFigmaConfig(filepath);
40+
})
3241

src/commander/exec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ command
2020
.argument('<command...>', 'Command supplied for running tests')
2121
.option('-P, --port <number>', 'Port number for the server')
2222
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
23+
.option('--buildName <string>', 'Specify the build name')
2324
.action(async function(execCommand, _, command) {
25+
const options = command.optsWithGlobals();
26+
if (options.buildName === '') {
27+
console.log(`Error: The '--buildName' option cannot be an empty string.`);
28+
process.exit(1);
29+
}
2430
let ctx: Context = ctxInit(command.optsWithGlobals());
2531

2632
if (!which.sync(execCommand[0], { nothrow: true })) {

src/commander/upload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ command
2626
return val.split(',').map(pattern => pattern.trim());
2727
})
2828
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
29+
.option('--buildName <string>', 'Specify the build name')
2930
.action(async function(directory, _, command) {
31+
const options = command.optsWithGlobals();
32+
if (options.buildName === '') {
33+
console.log(`Error: The '--buildName' option cannot be an empty string.`);
34+
process.exit(1);
35+
}
3036
let ctx: Context = ctxInit(command.optsWithGlobals());
3137

3238
if (!fs.existsSync(directory)) {

src/commander/uploadFigma.ts

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import fs from 'fs'
22
import { Command } from 'commander'
33
import { Context } from '../types.js'
4-
import { color , Listr, ListrDefaultRendererLogLevels, LoggerFormat } from 'listr2'
4+
import { color, Listr, ListrDefaultRendererLogLevels, LoggerFormat } from 'listr2'
55
import auth from '../tasks/auth.js'
66
import ctxInit from '../lib/ctx.js'
77
import getGitInfo from '../tasks/getGitInfo.js'
8-
import createBuild from '../tasks/createBuild.js'
9-
import captureScreenshots from '../tasks/captureScreenshots.js'
108
import finalizeBuild from '../tasks/finalizeBuild.js'
11-
import { validateFigmaDesignConfig } from '../lib/schemaValidation.js'
9+
import { validateFigmaDesignConfig, validateWebFigmaConfig } from '../lib/schemaValidation.js'
1210
import uploadFigmaDesigns from '../tasks/uploadFigmaDesigns.js'
11+
import uploadWebFigma from '../tasks/uploadWebFigma.js'
12+
import { verifyFigmaWebConfig } from '../lib/config.js'
13+
import chalk from 'chalk';
1314

14-
const command = new Command();
1515

16-
command
16+
const uploadFigma = new Command();
17+
const uploadWebFigmaCommand = new Command();
18+
19+
uploadFigma
1720
.name('upload-figma')
1821
.description('Capture screenshots of static sites')
1922
.argument('<file>', 'figma design config file')
2023
.option('--markBaseline', 'Mark the uploaded images as baseline')
21-
.option('--buildName <buildName>' , 'Name of the build')
22-
.action(async function(file, _, command) {
24+
.option('--buildName <buildName>', 'Name of the build')
25+
.action(async function (file, _, command) {
2326
let ctx: Context = ctxInit(command.optsWithGlobals());
2427

2528
if (!fs.existsSync(file)) {
@@ -62,4 +65,67 @@ command
6265

6366
})
6467

65-
export default command;
68+
uploadWebFigmaCommand
69+
.name('upload-figma-web')
70+
.description('Capture screenshots of static sites')
71+
.argument('<file>', 'figma config config file')
72+
.option('--markBaseline', 'Mark the uploaded images as baseline')
73+
.option('--buildName <buildName>', 'Name of the build')
74+
.action(async function (file, _, command) {
75+
let ctx: Context = ctxInit(command.optsWithGlobals());
76+
77+
if (!fs.existsSync(file)) {
78+
console.log(`Error: figma-web config file ${file} not found.`);
79+
return;
80+
}
81+
try {
82+
ctx.config = JSON.parse(fs.readFileSync(file, 'utf8'));
83+
ctx.log.info(JSON.stringify(ctx.config));
84+
if (!validateWebFigmaConfig(ctx.config)) {
85+
ctx.log.debug(JSON.stringify(validateWebFigmaConfig.errors, null, 2));
86+
// Iterate and add warning for "additionalProperties"
87+
validateWebFigmaConfig.errors?.forEach(error => {
88+
if (error.keyword === "additionalProperties") {
89+
ctx.log.warn(`Additional property "${error.params.additionalProperty}" is not allowed.`)
90+
} else {
91+
const validationError = error.message;
92+
throw new Error(validationError || 'Invalid figma-web config found in file : ' + file);
93+
}
94+
});
95+
}
96+
97+
//Validate the figma config
98+
verifyFigmaWebConfig(ctx);
99+
} catch (error: any) {
100+
ctx.log.error(chalk.red(`Invalid figma-web config; ${error.message}`));
101+
return;
102+
}
103+
104+
let tasks = new Listr<Context>(
105+
[
106+
auth(ctx),
107+
getGitInfo(ctx),
108+
uploadWebFigma(ctx),
109+
finalizeBuild(ctx)
110+
],
111+
{
112+
rendererOptions: {
113+
icon: {
114+
[ListrDefaultRendererLogLevels.OUTPUT]: `→`
115+
},
116+
color: {
117+
[ListrDefaultRendererLogLevels.OUTPUT]: color.gray as LoggerFormat
118+
}
119+
}
120+
}
121+
)
122+
123+
try {
124+
await tasks.run(ctx);
125+
} catch (error) {
126+
console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
127+
}
128+
129+
})
130+
131+
export { uploadFigma, uploadWebFigmaCommand }

src/dom/dom-serializer.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/config.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'path'
22
import fs from 'fs'
33
import constants from './constants.js';
4+
import { Context } from "../types.js";
45

56
export function createConfig(filepath: string) {
67
// default filepath
@@ -67,3 +68,59 @@ export function createFigmaConfig(filepath: string) {
6768
fs.writeFileSync(filepath, JSON.stringify(constants.DEFAULT_FIGMA_CONFIG, null, 2) + '\n');
6869
console.log(`Created designs config: ${filepath}`);
6970
};
71+
72+
export function createWebFigmaConfig(filepath: string) {
73+
// default filepath
74+
filepath = filepath || '.smartui.json';
75+
let filetype = path.extname(filepath);
76+
if (filetype != '.json') {
77+
console.log('Error: figma config file must have .json extension');
78+
return
79+
}
80+
81+
// verify the file does not already exist
82+
if (fs.existsSync(filepath)) {
83+
console.log(`Error: figma config already exists: ${filepath}`);
84+
console.log(`To create a new file, please specify the file name like: 'smartui config:create-figma-web <fileName>.json'`);
85+
return
86+
}
87+
88+
// write stringified default config options to the filepath
89+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
90+
fs.writeFileSync(filepath, JSON.stringify(constants.WEB_FIGMA_CONFIG, null, 2) + '\n');
91+
console.log(`Created figma web config: ${filepath}`);
92+
};
93+
94+
export function verifyFigmaWebConfig(ctx: Context) {
95+
if (ctx.env.FIGMA_TOKEN == "") {
96+
throw new Error("Missing FIGMA_TOKEN in Environment Variables");
97+
}
98+
if (ctx.env.LT_USERNAME == "") {
99+
throw new Error("Missing LT_USERNAME in Environment Variables");
100+
}
101+
if (ctx.env.LT_ACCESS_KEY == "") {
102+
throw new Error("Missing LT_ACCESS_KEY in Environment Variables");
103+
}
104+
let figma = ctx.config && ctx.config?.figma || {};
105+
const screenshots = [];
106+
for (let c of figma?.configs) {
107+
if (c.screenshot_names && c.screenshot_names.length > 0 && c.figma_ids && c.figma_ids.length != c.screenshot_names.length) {
108+
throw new Error("Mismatch in Figma Ids and Screenshot Names in figma config");
109+
}
110+
if (isValidArray(c.screenshot_names)) {
111+
for (const name of c.screenshot_names) {
112+
screenshots.push(name);
113+
}
114+
}
115+
}
116+
117+
if (new Set(screenshots).size !== screenshots.length) {
118+
throw new Error("Found duplicate screenshot names in figma config");
119+
}
120+
121+
return true;
122+
};
123+
124+
function isValidArray(input) {
125+
return Array.isArray(input) && input.length > 0;
126+
}

src/lib/constants.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,41 @@ export default {
321321
'iPhone XR': { os: 'ios', viewport: { width: 414, height: 896 } },
322322
'iPhone XS': { os: 'ios', viewport: { width: 375, height: 812 } },
323323
'iPhone XS Max': { os: 'ios', viewport: { width: 414, height: 896 } },
324+
'Galaxy A10s': { os: 'android', viewport: { width: 360, height: 640 } },
325+
'Galaxy A11': { os: 'android', viewport: { width: 412, height: 732 } },
326+
'Galaxy A13': { os: 'android', viewport: { width: 412, height: 732 } },
327+
'Galaxy A52s 5G': { os: 'android', viewport: { width: 384, height: 718 } },
328+
'Galaxy A53 5G': { os: 'android', viewport: { width: 412, height: 915 } },
329+
'Galaxy Tab A 10.1 (2019)': { os: 'android', viewport: { width: 800, height: 1280 } },
330+
'Galaxy Tab S9': { os: 'android', viewport: { width: 753, height: 1069 } },
331+
'Honor X9a 5G': { os: 'android', viewport: { width: 360, height: 678 } },
332+
'Huawei P30 Lite': { os: 'android', viewport: { width: 360, height: 647 } },
333+
'Huawei P50 Pro': { os: 'android', viewport: { width: 412, height: 915 } },
334+
'iPad Pro 13 (2024)': { os: 'ios', viewport: { width: 1032, height: 1376 } },
335+
'iPad Pro 11 (2024)': { os: 'ios', viewport: { width: 834, height: 1210 } },
336+
'iPad Air 13 (2024)': { os: 'ios', viewport: { width: 1024, height: 1366 } },
337+
'iPad Air 11 (2024)': { os: 'ios', viewport: { width: 820, height: 1180 } },
338+
'iPad 10.9 (2022)': { os: 'ios', viewport: { width: 820, height: 1180 } },
339+
'iPhone 16': { os: 'ios', viewport: { width: 393, height: 852 } },
340+
'iPhone 16 Plus': { os: 'ios', viewport: { width: 430, height: 932 } },
341+
'iPhone 16 Pro': { os: 'ios', viewport: { width: 402, height: 874 } },
342+
'iPhone 16 Pro Max': { os: 'ios', viewport: { width: 440, height: 956 } },
343+
'Motorola Edge 40': { os: 'android', viewport: { width: 412, height: 915 } },
344+
'Motorola Edge 30': { os: 'android', viewport: { width: 432, height: 814 } },
345+
'Moto G22': { os: 'android', viewport: { width: 412, height: 767 } },
346+
'Moto G54 5G': { os: 'android', viewport: { width: 432, height: 810 } },
347+
'Moto G71 5G': { os: 'android', viewport: { width: 412, height: 732 } },
348+
'Pixel Tablet': { os: 'android', viewport: { width: 800, height: 1100 } },
349+
'Pixel 6a': { os: 'android', viewport: { width: 412, height: 766 } },
350+
'Pixel 7a': { os: 'android', viewport: { width: 412, height: 766 } },
351+
'Pixel 9': { os: 'android', viewport: { width: 412, height: 924 } },
352+
'Pixel 9 Pro': { os: 'android', viewport: { width: 412, height: 915 } },
353+
'Pixel 9 Pro XL': { os: 'android', viewport: { width: 448, height: 998 } },
354+
'Redmi 9A': { os: 'android', viewport: { width: 360, height: 800 } },
355+
'Redmi Note 13 Pro': { os: 'android', viewport: { width: 412, height: 869 } },
356+
'Aquos Sense 5G': { os: 'android', viewport: { width: 393, height: 731 } },
357+
'Xperia 10 IV': { os: 'android', viewport: { width: 412, height: 832 } },
358+
'Honeywell CT40': { os: 'android', viewport: { width: 360, height: 512 } },
324359
},
325360

326361
FIGMA_API: 'https://api.figma.com/v1/',
@@ -335,5 +370,30 @@ export default {
335370
]
336371
}
337372
]
373+
},
374+
WEB_FIGMA_CONFIG: {
375+
web: {
376+
browsers: [
377+
'chrome',
378+
'firefox',
379+
'safari',
380+
'edge'
381+
]
382+
},
383+
figma: {
384+
"depth": 2,
385+
"configs": [
386+
{
387+
"figma_file_token": "<token>",
388+
"figma_ids": ["id-1", "id-2"],
389+
"screenshot_names": ["homepage", "about"]
390+
},
391+
{
392+
"figma_file_token": "<token>",
393+
"figma_ids": ["id-3", "id-4"],
394+
"screenshot_names": ["xyz", "abc"]
395+
},
396+
]
397+
}
338398
}
339399
}

0 commit comments

Comments
 (0)