Skip to content

Commit 81df253

Browse files
Merge pull request #292 from LambdaTest/stage
Release PR: 4.1.11
2 parents 6d3f180 + 04ff1f7 commit 81df253

18 files changed

+561
-28
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "4.1.11-beta.0",
3+
"version": "4.1.11",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"

src/commander/capture.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ command
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')
2222
.option('--buildName <string>', 'Specify the build name')
23+
.option('--userName <string>', 'Specify the LT username')
24+
.option('--accessKey <string>', 'Specify the LT accesskey')
2325
.action(async function(file, _, command) {
2426
const options = command.optsWithGlobals();
2527
if (options.buildName === '') {

src/commander/commander.ts

+4-2
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, configWebFigma} from './config.js'
3+
import { configWeb, configStatic, configFigma, configWebFigma, configAppFigma} from './config.js'
44
import capture from './capture.js'
55
import upload from './upload.js'
66
import { version } from '../../package.json'
7-
import { uploadFigma, uploadWebFigmaCommand } from './uploadFigma.js'
7+
import { uploadFigma, uploadWebFigmaCommand,uploadAppFigmaCommand } from './uploadFigma.js'
88
import startServer from './server.js';
99
import stopServer from './stopServer.js'
1010
import ping from './ping.js'
@@ -27,7 +27,9 @@ program
2727
.addCommand(configFigma)
2828
.addCommand(uploadFigma)
2929
.addCommand(configWebFigma)
30+
.addCommand(configAppFigma)
3031
.addCommand(uploadWebFigmaCommand)
32+
.addCommand(uploadAppFigmaCommand)
3133

3234

3335

src/commander/config.ts

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

44
export const configWeb = new Command();
55
export const configStatic = new Command();
66
export const configFigma = new Command();
77
export const configWebFigma = new Command();
8+
export const configAppFigma = new Command();
89

910

1011
configWeb
@@ -39,3 +40,10 @@ configWebFigma
3940
createWebFigmaConfig(filepath);
4041
})
4142

43+
configAppFigma
44+
.name('config:create-figma-app')
45+
.description('Create figma config file for mobile apps')
46+
.argument('[filepath]', 'Optional config filepath')
47+
.action(async function(filepath, options) {
48+
createAppFigmaConfig(filepath);
49+
})

src/commander/exec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ command
2222
.option('-P, --port <number>', 'Port number for the server')
2323
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
2424
.option('--buildName <string>', 'Specify the build name')
25+
.option('--userName <string>', 'Specify the LT username')
26+
.option('--accessKey <string>', 'Specify the LT accesskey')
2527
.action(async function(execCommand, _, command) {
2628
const options = command.optsWithGlobals();
2729
if (options.buildName === '') {

src/commander/upload.ts

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ command
2727
})
2828
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
2929
.option('--buildName <string>', 'Specify the build name')
30+
.option('--userName <string>', 'Specify the LT username')
31+
.option('--accessKey <string>', 'Specify the LT accesskey')
3032
.action(async function(directory, _, command) {
3133
const options = command.optsWithGlobals();
3234
if (options.buildName === '') {

src/commander/uploadFigma.ts

+73-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import auth from '../tasks/auth.js'
66
import ctxInit from '../lib/ctx.js'
77
import getGitInfo from '../tasks/getGitInfo.js'
88
import finalizeBuild from '../tasks/finalizeBuild.js'
9-
import { validateFigmaDesignConfig, validateWebFigmaConfig } from '../lib/schemaValidation.js'
9+
import { validateFigmaDesignConfig, validateWebFigmaConfig, validateAppFigmaConfig } from '../lib/schemaValidation.js'
1010
import uploadFigmaDesigns from '../tasks/uploadFigmaDesigns.js'
1111
import uploadWebFigma from '../tasks/uploadWebFigma.js'
12+
import uploadAppFigma from '../tasks/uploadAppFigma.js'
1213
import { verifyFigmaWebConfig } from '../lib/config.js'
1314
import chalk from 'chalk';
1415

1516

1617
const uploadFigma = new Command();
1718
const uploadWebFigmaCommand = new Command();
19+
const uploadAppFigmaCommand = new Command();
20+
1821

1922
uploadFigma
2023
.name('upload-figma')
@@ -68,7 +71,7 @@ uploadFigma
6871

6972
uploadWebFigmaCommand
7073
.name('upload-figma-web')
71-
.description('Capture screenshots of static sites')
74+
.description('Capture figma screenshots into CLI build')
7275
.argument('<file>', 'figma config config file')
7376
.option('--markBaseline', 'Mark the uploaded images as baseline')
7477
.option('--buildName <buildName>', 'Name of the build')
@@ -130,4 +133,71 @@ uploadWebFigmaCommand
130133

131134
})
132135

133-
export { uploadFigma, uploadWebFigmaCommand }
136+
137+
138+
uploadAppFigmaCommand
139+
.name('upload-figma-app')
140+
.description('Capture figma screenshots into App Build')
141+
.argument('<file>', 'figma config config file')
142+
.option('--markBaseline', 'Mark the uploaded images as baseline')
143+
.option('--buildName <buildName>', 'Name of the build')
144+
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
145+
.action(async function (file, _, command) {
146+
let ctx: Context = ctxInit(command.optsWithGlobals());
147+
148+
if (!fs.existsSync(file)) {
149+
console.log(`Error: figma-app config file ${file} not found.`);
150+
return;
151+
}
152+
try {
153+
ctx.config = JSON.parse(fs.readFileSync(file, 'utf8'));
154+
ctx.log.info(JSON.stringify(ctx.config));
155+
if (!validateAppFigmaConfig(ctx.config)) {
156+
ctx.log.debug(JSON.stringify(validateAppFigmaConfig.errors, null, 2));
157+
// Iterate and add warning for "additionalProperties"
158+
validateAppFigmaConfig.errors?.forEach(error => {
159+
if (error.keyword === "additionalProperties") {
160+
ctx.log.warn(`Additional property "${error.params.additionalProperty}" is not allowed.`)
161+
} else {
162+
const validationError = error.message;
163+
throw new Error(validationError || 'Invalid figma-app config found in file : ' + file);
164+
}
165+
});
166+
}
167+
168+
//Validate the figma config
169+
verifyFigmaWebConfig(ctx);
170+
} catch (error: any) {
171+
ctx.log.error(chalk.red(`Invalid figma-app config; ${error.message}`));
172+
return;
173+
}
174+
175+
let tasks = new Listr<Context>(
176+
[
177+
auth(ctx),
178+
getGitInfo(ctx),
179+
uploadAppFigma(ctx),
180+
finalizeBuild(ctx)
181+
],
182+
{
183+
rendererOptions: {
184+
icon: {
185+
[ListrDefaultRendererLogLevels.OUTPUT]: `→`
186+
},
187+
color: {
188+
[ListrDefaultRendererLogLevels.OUTPUT]: color.gray as LoggerFormat
189+
}
190+
}
191+
}
192+
)
193+
194+
try {
195+
await tasks.run(ctx);
196+
} catch (error) {
197+
console.log('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
198+
}
199+
200+
})
201+
202+
203+
export { uploadFigma, uploadWebFigmaCommand, uploadAppFigmaCommand }

src/lib/config.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,43 @@ export function verifyFigmaWebConfig(ctx: Context) {
118118
throw new Error("Found duplicate screenshot names in figma config");
119119
}
120120

121-
return true;
122-
};
121+
let mobileConfig = ctx.config?.mobile || {}
122+
// Iterate over mobileConfig array and get viewport for each device
123+
if (Array.isArray(mobileConfig)) {
124+
for (const config of mobileConfig) {
125+
const deviceName = config.name;
126+
if (constants.SUPPORTED_MOBILE_DEVICES[deviceName]) {
127+
const deviceData = constants.SUPPORTED_MOBILE_DEVICES[deviceName];
128+
config.width = deviceData.viewport.width;
129+
config.height = deviceData.viewport.height;
130+
}
131+
}
132+
}
133+
}
123134

124135
function isValidArray(input) {
125136
return Array.isArray(input) && input.length > 0;
126-
}
137+
}
138+
139+
140+
export function createAppFigmaConfig(filepath: string) {
141+
// default filepath
142+
filepath = filepath || '.smartui.json';
143+
let filetype = path.extname(filepath);
144+
if (filetype != '.json') {
145+
console.log('Error: figma app config file must have .json extension');
146+
return
147+
}
148+
149+
// verify the file does not already exist
150+
if (fs.existsSync(filepath)) {
151+
console.log(`Error: figma app config already exists: ${filepath}`);
152+
console.log(`To create a new figma app config, please specify the file name like: 'smartui config:create-figma-app <fileName>.json'`);
153+
return
154+
}
155+
156+
// write stringified default config options to the filepath
157+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
158+
fs.writeFileSync(filepath, JSON.stringify(constants.APP_FIGMA_CONFIG, null, 2) + '\n');
159+
console.log(`Created figma app config: ${filepath}`);
160+
};

src/lib/constants.ts

+36-9
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ export default {
9696
// sets navigator.webdriver to false
9797
'--disable-blink-features=AutomationControlled',
9898
// disable UA-CH feature
99-
`--disable-features=UserAgentClientHint`,
99+
`--disable-features=UserAgentClientHint`,
100100
],
101101

102102
// discovery request headers
103103
REQUEST_HEADERS: {
104104
// `HeadlessChrome` is added to sec-ch-ua, `--disable-features=UserAgentClientHint` doesn't seem to work
105-
'sec-ch-ua':'"Chromium";v="129", "Not=A?Brand";v="8"',
105+
'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
106106
'sec-ch-ua-mobile': '"?0"',
107107
'sec-ch-ua-platform': '"Windows"'
108108
},
@@ -384,16 +384,43 @@ export default {
384384
"depth": 2,
385385
"configs": [
386386
{
387-
"figma_file_token": "<token>",
388-
"figma_ids": ["id-1", "id-2"],
389-
"screenshot_names": ["homepage", "about"]
387+
"figma_file_token": "<token>",
388+
"figma_ids": ["id-1", "id-2"],
389+
"screenshot_names": ["homepage", "about"]
390390
},
391391
{
392-
"figma_file_token": "<token>",
393-
"figma_ids": ["id-3", "id-4"],
394-
"screenshot_names": ["xyz", "abc"]
392+
"figma_file_token": "<token>",
393+
"figma_ids": ["id-3", "id-4"],
394+
"screenshot_names": ["xyz", "abc"]
395395
},
396-
]
396+
]
397+
}
398+
},
399+
APP_FIGMA_CONFIG: {
400+
mobile: [
401+
{
402+
"name": "Pixel 8",
403+
"platform": ["android 14"]
404+
},
405+
{
406+
"name": "iPhone 15",
407+
"platform": ["ios 17"]
408+
}
409+
],
410+
figma: {
411+
"depth": 2,
412+
"configs": [
413+
{
414+
"figma_file_token": "<token>",
415+
"figma_ids": ["id-1", "id-2"],
416+
"screenshot_names": ["homepage", "about"]
417+
},
418+
{
419+
"figma_file_token": "<token>",
420+
"figma_ids": ["id-3", "id-4"],
421+
"screenshot_names": ["xyz", "abc"]
422+
},
423+
]
397424
}
398425
}
399426
}

src/lib/ctx.ts

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export default (options: Record<string, string>): Context => {
6060
fetchResultsFileObj = ''
6161
}
6262
buildNameObj = options.buildName || ''
63+
if (options.userName && options.accessKey) {
64+
env.LT_USERNAME = options.userName
65+
env.LT_ACCESS_KEY = options.accessKey
66+
}
6367
} catch (error: any) {
6468
console.log(`[smartui] Error: ${error.message}`);
6569
process.exit();

src/lib/env.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export default (): Env => {
1919
CURRENT_BRANCH,
2020
PROJECT_NAME,
2121
SMARTUI_API_PROXY,
22-
SMARTUI_API_SKIP_CERTIFICATES
22+
SMARTUI_API_SKIP_CERTIFICATES,
23+
USE_REMOTE_DISCOVERY
2324
} = process.env
2425

2526
return {
@@ -40,6 +41,7 @@ export default (): Env => {
4041
SMARTUI_DO_NOT_USE_CAPTURED_COOKIES: SMARTUI_DO_NOT_USE_CAPTURED_COOKIES === 'true',
4142
PROJECT_NAME,
4243
SMARTUI_API_PROXY,
43-
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true'
44+
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true',
45+
USE_REMOTE_DISCOVERY: USE_REMOTE_DISCOVERY === 'true'
4446
}
4547
}

src/lib/httpClient.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export default class httpClient {
330330
type: ctx.testType,
331331
source: 'cli'
332332
},
333-
async: false,
333+
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
334334
discoveryErrors: discoveryErrors,
335335
}
336336
}, ctx.log)
@@ -352,7 +352,7 @@ export default class httpClient {
352352
type: ctx.testType,
353353
source: 'cli'
354354
},
355-
async: false,
355+
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
356356
discoveryErrors: discoveryErrors,
357357
}
358358
}, ctx.log)

0 commit comments

Comments
 (0)