Skip to content

Commit 2ec715c

Browse files
authored
Merge branch 'stage' into add_auth_to_proxy
2 parents 7f79fdd + 0f1f252 commit 2ec715c

29 files changed

+1841
-202
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,18 @@
4343
"json-stringify-safe": "^5.0.1",
4444
"listr2": "^7.0.1",
4545
"node-cache": "^5.1.2",
46+
"postcss": "^8.5.6",
4647
"sharp": "^0.33.4",
4748
"tsup": "^7.2.0",
4849
"uuid": "^11.0.3",
4950
"which": "^4.0.0",
5051
"winston": "^3.10.0"
5152
},
53+
"overrides": {
54+
"simple-swizzle": "0.2.2"
55+
},
5256
"devDependencies": {
57+
"find-free-port": "^2.0.0",
5358
"typescript": "^5.3.2"
5459
}
5560
}

src/commander/commander.ts

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

1415
const program = new Command();
1516

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

4244

4345

src/commander/exec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { color, Listr, ListrDefaultRendererLogLevels } from 'listr2'
55
import startServer from '../tasks/startServer.js'
66
import authExec from '../tasks/authExec.js'
77
import ctxInit from '../lib/ctx.js'
8+
import commandOptionsInit from '../lib/execCommandOptions.js'
89
import getGitInfo from '../tasks/getGitInfo.js'
910
import createBuildExec from '../tasks/createBuildExec.js'
1011
import exec from '../tasks/exec.js'
@@ -25,6 +26,7 @@ command
2526
.option('--scheduled <string>', 'Specify the schedule ID')
2627
.option('--userName <string>', 'Specify the LT username')
2728
.option('--accessKey <string>', 'Specify the LT accesskey')
29+
.option('--show-render-errors', 'Show render errors from SmartUI build')
2830
.action(async function(execCommand, _, command) {
2931
const options = command.optsWithGlobals();
3032
if (options.buildName === '') {
@@ -40,6 +42,9 @@ command
4042
ctx.args.execCommand = execCommand
4143
ctx.snapshotQueue = new snapshotQueue(ctx)
4244
ctx.totalSnapshots = 0
45+
ctx.sourceCommand = 'exec'
46+
47+
commandOptionsInit(ctx);
4348

4449
let tasks = new Listr<Context>(
4550
[

src/commander/server.ts

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

1213
const command = new Command();
1314

@@ -27,13 +28,15 @@ command
2728
ctx.snapshotQueue = new snapshotQueue(ctx);
2829
ctx.totalSnapshots = 0
2930
ctx.isStartExec = true
30-
31+
ctx.sourceCommand = 'exec-start'
32+
3133
let tasks = new Listr<Context>(
3234
[
33-
auth(ctx),
35+
authExec(ctx),
3436
startServer(ctx),
3537
getGitInfo(ctx),
36-
createBuild(ctx),
38+
...(ctx.config.tunnel && ctx.config.tunnel?.type === 'auto' ? [startTunnel(ctx)] : []),
39+
createBuildExec(ctx),
3740

3841
],
3942
{
@@ -50,14 +53,16 @@ command
5053

5154
try {
5255
await tasks.run(ctx);
53-
startPingPolling(ctx);
54-
if (ctx.options.fetchResults) {
56+
if (ctx.build && ctx.build.id && !ctx.autoTunnelStarted) {
57+
startPingPolling(ctx);
58+
}
59+
if (ctx.options.fetchResults && ctx.build && ctx.build.id) {
5560
startPolling(ctx, '', false, '')
5661
}
57-
58-
62+
5963
} catch (error) {
6064
console.error('Error during server execution:', error);
65+
process.exit(1);
6166
}
6267
});
6368

src/commander/stopServer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ command
3333
}
3434
} catch (error: any) {
3535
// Handle any errors during the HTTP request
36-
if (error.code === 'ECONNABORTED') {
36+
if (error && error.code === 'ECONNABORTED') {
3737
console.error(chalk.red('Error: SmartUI server did not respond in 15 seconds'));
38+
} if (error && error.code === 'ECONNREFUSED') {
39+
console.error(chalk.red('Error: Looks like smartui cli server is already stopped'));
3840
} else {
3941
console.error(chalk.red('Error while stopping server'));
4042
}

src/commander/uploadPdf.ts

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

src/lib/constants.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default {
2525
waitForTimeout: 1000,
2626
enableJavaScript: false,
2727
allowedHostnames: [],
28-
smartIgnore: false
28+
smartIgnore: false,
29+
showRenderErrors: false
2930
},
3031
DEFAULT_WEB_STATIC_CONFIG: [
3132
{
@@ -46,6 +47,8 @@ export default {
4647
EDGE: 'edge',
4748
EDGE_CHANNEL: 'msedge',
4849
WEBKIT: 'webkit',
50+
MIN_PORT_RANGE: 49100,
51+
MAX_PORT_RANGE: 60000,
4952

5053
// discovery browser launch arguments
5154
LAUNCH_ARGS: [
@@ -123,6 +126,7 @@ export default {
123126
MOBILE_ORIENTATION_LANDSCAPE: 'landscape',
124127

125128
// build status
129+
BUILD_RUNNING: 'running',
126130
BUILD_COMPLETE: 'completed',
127131
BUILD_ERROR: 'error',
128132

@@ -356,6 +360,22 @@ export default {
356360
'Aquos Sense 5G': { os: 'android', viewport: { width: 393, height: 731 } },
357361
'Xperia 10 IV': { os: 'android', viewport: { width: 412, height: 832 } },
358362
'Honeywell CT40': { os: 'android', viewport: { width: 360, height: 512 } },
363+
'Galaxy S25': { os: 'android', viewport: { width: 370, height: 802 } },
364+
'Galaxy S25 Plus': { os: 'android', viewport: { width: 393, height: 888 } },
365+
'Galaxy S25 Ultra': { os: 'android', viewport: { width: 432, height: 941 } },
366+
'iPhone 17': { os: 'ios', viewport: { width: 393, height: 852 } },
367+
'iPhone 17 Pro': { os: 'ios', viewport: { width: 393, height: 852 } },
368+
'iPhone 17 Pro Max': { os: 'ios', viewport: { width: 430, height: 932 } },
369+
'Galaxy Z Fold7': { os: 'android', viewport: { width: 373, height: 873 } },
370+
'Galaxy Z Flip7': { os: 'android', viewport: { width: 299, height: 723 } },
371+
'Galaxy Z Fold6': { os: 'android', viewport: { width: 373, height: 873 } },
372+
'Galaxy Z Flip6': { os: 'android', viewport: { width: 298, height: 713 } },
373+
'Pixel 10 Pro': { os: 'android', viewport: { width: 393, height: 852 } },
374+
'Pixel 10 Pro XL': { os: 'android', viewport: { width: 412, height: 915 } },
375+
'Motorola Edge 50 Pro': { os: 'android', viewport: { width: 384, height: 864 } },
376+
'OnePlus 12': { os: 'android', viewport: { width: 384, height: 884 } },
377+
'Nothing Phone 1': { os: 'android', viewport: { width: 393, height: 853 } },
378+
'Nothing Phone 2': { os: 'android', viewport: { width: 393, height: 878 } },
359379
},
360380

361381
FIGMA_API: 'https://api.figma.com/v1/',

src/lib/ctx.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import logger from './logger.js'
66
import getEnv from './env.js'
77
import httpClient from './httpClient.js'
88
import fs from 'fs'
9+
import { resolveCustomCSS } from './utils.js'
910

1011
export default (options: Record<string, string>): Context => {
1112
let env: Env = getEnv();
@@ -25,6 +26,9 @@ export default (options: Record<string, string>): Context => {
2526
let buildNameObj: string;
2627
let allowDuplicateSnapshotNames: boolean = false;
2728
let useLambdaInternal: boolean = false;
29+
let useRemoteDiscovery: boolean = false;
30+
let useExtendedViewport: boolean = false;
31+
let loadDomContent: boolean = false;
2832
try {
2933
if (options.config) {
3034
config = JSON.parse(fs.readFileSync(options.config, 'utf-8'));
@@ -37,12 +41,32 @@ export default (options: Record<string, string>): Context => {
3741
delete config.web.resolutions;
3842
}
3943

44+
if(config.approvalThreshold && config.rejectionThreshold) {
45+
if(config.rejectionThreshold <= config.approvalThreshold) {
46+
throw new Error('Invalid config; rejectionThreshold must be greater than approvalThreshold');
47+
}
48+
}
49+
4050
let validateConfigFn = options.scheduled ? validateConfigForScheduled : validateConfig;
4151

4252
// validate config
4353
if (!validateConfigFn(config)) {
4454
throw new Error(validateConfigFn.errors[0].message);
4555
}
56+
57+
// Resolve customCSS if provided
58+
if ((config as any).customCSS) {
59+
try {
60+
(config as any).customCSS = resolveCustomCSS(
61+
(config as any).customCSS,
62+
options.config,
63+
logger
64+
);
65+
logger.debug('Successfully resolved and validated customCSS from config');
66+
} catch (error: any) {
67+
throw new Error(`customCSS error: ${error.message}`);
68+
}
69+
}
4670
} else {
4771
logger.info("## No config file provided. Using default config.");
4872
}
@@ -71,6 +95,8 @@ export default (options: Record<string, string>): Context => {
7195
if (options.userName && options.accessKey) {
7296
env.LT_USERNAME = options.userName
7397
env.LT_ACCESS_KEY = options.accessKey
98+
// process.env.LT_USERNAME = options.userName
99+
// process.env.LT_ACCESS_KEY = options.accessKey
74100
}
75101
} catch (error: any) {
76102
console.log(`[smartui] Error: ${error.message}`);
@@ -100,6 +126,15 @@ export default (options: Record<string, string>): Context => {
100126
if (config.useLambdaInternal) {
101127
useLambdaInternal = true;
102128
}
129+
if (config.useRemoteDiscovery) {
130+
useRemoteDiscovery = true;
131+
}
132+
if (config.useExtendedViewport) {
133+
useExtendedViewport = true;
134+
}
135+
if (config.loadDomContent) {
136+
loadDomContent = true;
137+
}
103138

104139
//if config.waitForPageRender has value and if its less than 30000 then make it to 30000 default
105140
if (config.waitForPageRender && config.waitForPageRender < 30000) {
@@ -132,6 +167,13 @@ export default (options: Record<string, string>): Context => {
132167
requestHeaders: config.requestHeaders || {},
133168
allowDuplicateSnapshotNames: allowDuplicateSnapshotNames,
134169
useLambdaInternal: useLambdaInternal,
170+
useRemoteDiscovery: useRemoteDiscovery,
171+
useExtendedViewport: useExtendedViewport,
172+
loadDomContent: loadDomContent,
173+
approvalThreshold: config.approvalThreshold,
174+
rejectionThreshold: config.rejectionThreshold,
175+
showRenderErrors: config.showRenderErrors ?? false,
176+
customCSS: (config as any).customCSS
135177
},
136178
uploadFilePath: '',
137179
webStaticConfig: [],
@@ -169,14 +211,18 @@ export default (options: Record<string, string>): Context => {
169211
fetchResultsFileName: fetchResultsFileObj,
170212
baselineBranch: options.baselineBranch || '',
171213
baselineBuild: options.baselineBuild || '',
172-
githubURL : options.githubURL || ''
214+
githubURL : options.githubURL || '',
215+
showRenderErrors: options.showRenderErrors ? true : false,
216+
userName: options.userName || '',
217+
accessKey: options.accessKey || ''
173218
},
174219
cliVersion: version,
175220
totalSnapshots: -1,
176221
isStartExec: false,
177222
isSnapshotCaptured: false,
178223
sessionCapabilitiesMap: new Map<string, any[]>(),
179224
buildToSnapshotCountMap: new Map<string, number>(),
225+
sessionIdToSnapshotNameMap: new Map<string, string[]>(),
180226
fetchResultsForBuild: new Array<string>,
181227
orgId: 0,
182228
userId: 0,

src/lib/env.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default (): Env => {
44
const {
55
PROJECT_TOKEN = '',
66
SMARTUI_CLIENT_API_URL = 'https://api.lambdatest.com/visualui/1.0',
7+
SMARTUI_UPLOAD_URL = 'https://api.lambdatest.com',
78
SMARTUI_GIT_INFO_FILEPATH,
89
SMARTUI_DO_NOT_USE_CAPTURED_COOKIES,
910
HTTP_PROXY,
@@ -21,12 +22,17 @@ export default (): Env => {
2122
SMARTUI_API_PROXY,
2223
SMARTUI_API_SKIP_CERTIFICATES,
2324
USE_REMOTE_DISCOVERY,
24-
SMART_GIT
25+
SMART_GIT,
26+
SHOW_RENDER_ERRORS,
27+
SMARTUI_SSE_URL='https://server-events.lambdatest.com',
28+
LT_SDK_SKIP_EXECUTION_LOGS,
29+
MAX_CONCURRENT_PROCESSING
2530
} = process.env
2631

2732
return {
2833
PROJECT_TOKEN,
2934
SMARTUI_CLIENT_API_URL,
35+
SMARTUI_UPLOAD_URL: SMARTUI_UPLOAD_URL,
3036
SMARTUI_GIT_INFO_FILEPATH,
3137
HTTP_PROXY,
3238
HTTPS_PROXY,
@@ -44,6 +50,10 @@ export default (): Env => {
4450
SMARTUI_API_PROXY,
4551
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true',
4652
USE_REMOTE_DISCOVERY: USE_REMOTE_DISCOVERY === 'true',
47-
SMART_GIT: SMART_GIT === 'true'
53+
SMART_GIT: SMART_GIT === 'true',
54+
SHOW_RENDER_ERRORS: SHOW_RENDER_ERRORS === 'true',
55+
SMARTUI_SSE_URL,
56+
LT_SDK_SKIP_EXECUTION_LOGS: LT_SDK_SKIP_EXECUTION_LOGS === 'true',
57+
MAX_CONCURRENT_PROCESSING: MAX_CONCURRENT_PROCESSING ? parseInt(MAX_CONCURRENT_PROCESSING, 10) : 0,
4858
}
4959
}

0 commit comments

Comments
 (0)