Skip to content

Commit a0cd6f1

Browse files
authored
Merge pull request #231 from LambdaTest/stage
Release 28th Feb
2 parents 719312a + d89ad6b commit a0cd6f1

15 files changed

+173
-74
lines changed

.github/workflows/release.yaml

+17-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,23 @@ jobs:
2727
- name: Install dependencies
2828
run: pnpm install
2929

30-
- name: Release the package
31-
run: pnpm run release
30+
- name: Check if beta release
31+
id: check-beta
32+
run: |
33+
if [[ ${{ github.ref_name }} =~ -beta ]]; then
34+
echo "IS_BETA=true" >> $GITHUB_OUTPUT
35+
else
36+
echo "IS_BETA=false" >> $GITHUB_OUTPUT
37+
fi
38+
39+
- name: Release beta package
40+
if: steps.check-beta.outputs.IS_BETA == 'true'
41+
run: pnpm run release:beta
3242
env:
3343
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3444

35-
45+
- name: Release stable package
46+
if: steps.check-beta.outputs.IS_BETA == 'false'
47+
run: pnpm run release
48+
env:
49+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "4.1.1",
3+
"version": "4.1.2-beta.0",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"
@@ -38,6 +38,7 @@
3838
"fastify": "^4.24.3",
3939
"form-data": "^4.0.0",
4040
"listr2": "^7.0.1",
41+
"node-cache": "^5.1.2",
4142
"sharp": "^0.33.4",
4243
"tsup": "^7.2.0",
4344
"uuid": "^11.0.3",

src/commander/uploadFigma.ts

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ uploadWebFigmaCommand
7272
.argument('<file>', 'figma config config file')
7373
.option('--markBaseline', 'Mark the uploaded images as baseline')
7474
.option('--buildName <buildName>', 'Name of the build')
75+
.option('--fetch-results [filename]', 'Fetch results and optionally specify an output file, e.g., <filename>.json')
7576
.action(async function (file, _, command) {
7677
let ctx: Context = ctxInit(command.optsWithGlobals());
7778

src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import httpClient from './lib/httpClient.js'
66
import logger from './lib/logger.js'
77
import chalk from 'chalk'
88
import pkgJSON from './../package.json'
9+
import constants from './lib/constants.js';
10+
import fs from 'fs';
911

1012
(async function() {
1113
let client = new httpClient(getEnv());
1214
let log = logger;
1315

1416
try {
17+
// Delete log file
18+
fs.unlinkSync(constants.LOG_FILE_PATH);
1519
let { data: { latestVersion, deprecated, additionalDescription } } = await client.checkUpdate(log);
1620
log.info(`\nLambdaTest SmartUI CLI v${pkgJSON.version}`);
1721
log.info(chalk.yellow(`${additionalDescription}`));

src/lib/ctx.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,16 @@ export default (options: Record<string, string>): Context => {
8888
mobile: mobileConfig,
8989
waitForPageRender: config.waitForPageRender || 0,
9090
waitForTimeout: config.waitForTimeout || 0,
91+
waitForDiscovery: config.waitForDiscovery || 30000,
9192
enableJavaScript: config.enableJavaScript ?? false,
9293
cliEnableJavaScript: config.cliEnableJavaScript ?? true,
9394
scrollTime: config.scrollTime || constants.DEFAULT_SCROLL_TIME,
9495
allowedHostnames: config.allowedHostnames || [],
9596
basicAuthorization: basicAuthObj,
9697
smartIgnore: config.smartIgnore ?? false,
97-
delayedUpload: config.delayedUpload ?? false
98+
delayedUpload: config.delayedUpload ?? false,
99+
useGlobalCache: config.useGlobalCache ?? false,
100+
ignoreHTTPSErrors: config.ignoreHTTPSErrors ?? false,
98101
},
99102
uploadFilePath: '',
100103
webStaticConfig: [],

src/lib/httpClient.ts

+97-62
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export default class httpClient {
2222

2323
let proxyUrl = null;
2424
try {
25-
// Handle URL with or without protocol
26-
const urlStr = SMARTUI_API_PROXY?.startsWith('http') ?
27-
SMARTUI_API_PROXY : `http://${SMARTUI_API_PROXY}`;
25+
// Handle URL with or without protocol
26+
const urlStr = SMARTUI_API_PROXY?.startsWith('http') ?
27+
SMARTUI_API_PROXY : `http://${SMARTUI_API_PROXY}`;
2828
proxyUrl = SMARTUI_API_PROXY ? new URL(urlStr) : null;
2929
} catch (error) {
3030
console.error('Invalid proxy URL:', error);
@@ -36,7 +36,7 @@ export default class httpClient {
3636
port: proxyUrl.port ? Number(proxyUrl.port) : 80
3737
} : false
3838
};
39-
39+
4040
if (SMARTUI_API_SKIP_CERTIFICATES) {
4141
axiosConfig.httpsAgent = new https.Agent({
4242
rejectUnauthorized: false
@@ -45,29 +45,63 @@ export default class httpClient {
4545

4646
this.axiosInstance = axios.create(axiosConfig);
4747

48-
48+
4949
this.axiosInstance.interceptors.request.use((config) => {
5050
config.headers['projectToken'] = this.projectToken;
5151
config.headers['projectName'] = this.projectName;
5252
config.headers['username'] = this.username;
5353
config.headers['accessKey'] = this.accessKey;
5454
return config;
5555
});
56+
57+
// Add a request interceptor for retry logic
58+
this.axiosInstance.interceptors.response.use(
59+
(response) => response,
60+
async (error) => {
61+
const { config } = error;
62+
if (config && config.url === '/screenshot' && config.method === 'post') {
63+
// Set default retry count and delay if not already defined
64+
if (!config.retryCount) {
65+
config.retryCount = 0;
66+
config.retry = 2;
67+
config.retryDelay = 5000;
68+
}
69+
70+
// Check if we should retry the request
71+
if (config.retryCount < config.retry) {
72+
config.retryCount += 1;
73+
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
74+
config.timeout = 30000;
75+
return this.axiosInstance(config);
76+
}
77+
78+
// If we've reached max retries, reject with the error
79+
return Promise.reject(error);
80+
}
81+
}
82+
);
5683
}
5784

85+
86+
5887
async request(config: AxiosRequestConfig, log: Logger): Promise<Record<string, any>> {
5988
log.debug(`http request: ${config.method} ${config.url}`);
60-
if(config && config.data && !config.data.name) {
89+
if (config && config.data && !config.data.name) {
6190
log.debug(config.data);
6291
}
6392
return this.axiosInstance.request(config)
6493
.then(resp => {
65-
log.debug(`http response: ${JSON.stringify({
66-
status: resp.status,
67-
headers: resp.headers,
68-
body: resp.data
69-
})}`)
70-
return resp.data;
94+
if (resp) {
95+
log.debug(`http response: ${JSON.stringify({
96+
status: resp.status,
97+
headers: resp.headers,
98+
body: resp.data
99+
})}`)
100+
return resp.data;
101+
} else {
102+
log.debug(`empty response: ${JSON.stringify(resp)}`)
103+
return {};
104+
}
71105
})
72106
.catch(error => {
73107
if (error.response) {
@@ -107,7 +141,7 @@ export default class httpClient {
107141
throw new Error('Authentication failed, project token not received');
108142
}
109143
}
110-
144+
111145
createBuild(git: Git, config: any, log: Logger, buildName: string, isStartExec: boolean) {
112146
return this.request({
113147
url: '/build',
@@ -128,7 +162,7 @@ export default class httpClient {
128162
params: { buildId, baseline }
129163
}, log);
130164
}
131-
165+
132166
ping(buildId: string, log: Logger) {
133167
return this.request({
134168
url: '/build/ping',
@@ -138,10 +172,10 @@ export default class httpClient {
138172
}
139173
}, log);
140174
}
141-
175+
142176

143177
finalizeBuild(buildId: string, totalSnapshots: number, log: Logger) {
144-
let params: Record<string, string | number> = {buildId};
178+
let params: Record<string, string | number> = { buildId };
145179
if (totalSnapshots > -1) params.totalSnapshots = totalSnapshots;
146180

147181
return this.request({
@@ -156,7 +190,7 @@ export default class httpClient {
156190
url: `/builds/${ctx.build.id}/snapshot`,
157191
method: 'POST',
158192
headers: { 'Content-Type': 'application/json' },
159-
data: {
193+
data: {
160194
snapshot,
161195
test: {
162196
type: ctx.testType,
@@ -171,7 +205,7 @@ export default class httpClient {
171205
url: `/build/${ctx.build.id}/snapshot`,
172206
method: 'POST',
173207
headers: { 'Content-Type': 'application/json' },
174-
data: {
208+
data: {
175209
name: snapshot.name,
176210
url: snapshot.url,
177211
snapshotUuid: snapshotUuid,
@@ -185,13 +219,13 @@ export default class httpClient {
185219
}
186220

187221
uploadScreenshot(
188-
{ id: buildId, name: buildName, baseline }: Build,
189-
ssPath: string, ssName: string, browserName :string, viewport: string, log: Logger
222+
{ id: buildId, name: buildName, baseline }: Build,
223+
ssPath: string, ssName: string, browserName: string, viewport: string, log: Logger
190224
) {
191225
browserName = browserName === constants.SAFARI ? constants.WEBKIT : browserName;
192226
const file = fs.readFileSync(ssPath);
193227
const form = new FormData();
194-
form.append('screenshot', file, { filename: `${ssName}.png`, contentType: 'image/png'});
228+
form.append('screenshot', file, { filename: `${ssName}.png`, contentType: 'image/png' });
195229
form.append('browser', browserName);
196230
form.append('viewport', viewport);
197231
form.append('buildId', buildId);
@@ -204,19 +238,20 @@ export default class httpClient {
204238
method: 'POST',
205239
headers: form.getHeaders(),
206240
data: form,
241+
timeout: 30000
207242
})
208-
.then(() => {
209-
log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
210-
})
211-
.catch(error => {
212-
log.error(`Unable to upload screenshot ${JSON.stringify(error)}`)
213-
if (error && error.response && error.response.data && error.response.data.error) {
214-
throw new Error(error.response.data.error.message);
215-
}
216-
if (error) {
217-
throw new Error(JSON.stringify(error));
218-
}
219-
})
243+
.then(() => {
244+
log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
245+
})
246+
.catch(error => {
247+
log.error(`Unable to upload screenshot ${JSON.stringify(error)}`)
248+
if (error && error.response && error.response.data && error.response.data.error) {
249+
throw new Error(error.response.data.error.message);
250+
}
251+
if (error) {
252+
throw new Error(JSON.stringify(error));
253+
}
254+
})
220255
}
221256

222257
checkUpdate(log: Logger) {
@@ -232,15 +267,15 @@ export default class httpClient {
232267
}
233268

234269
getFigmaFilesAndImages(figmaFileToken: string, figmaToken: String | undefined, queryParams: string, authToken: string, depth: number, markBaseline: boolean, buildName: string, log: Logger) {
235-
const requestBody = {
236-
figma_file_token: figmaFileToken,
237-
figma_token: figmaToken,
238-
query_params: queryParams,
239-
auth: authToken,
240-
depth: depth,
241-
mark_base_line: markBaseline,
242-
build_name: buildName
243-
};
270+
const requestBody = {
271+
figma_file_token: figmaFileToken,
272+
figma_token: figmaToken,
273+
query_params: queryParams,
274+
auth: authToken,
275+
depth: depth,
276+
mark_base_line: markBaseline,
277+
build_name: buildName
278+
};
244279

245280
return this.request({
246281
url: "/uploadfigma",
@@ -297,34 +332,34 @@ export default class httpClient {
297332
return this.request({
298333
url: uploadURL,
299334
method: 'PUT',
300-
headers:{
335+
headers: {
301336
'Content-Type': 'application/json',
302337
},
303338
data: snapshot,
304339
maxBodyLength: Infinity, // prevent axios from limiting the body size
305340
maxContentLength: Infinity, // prevent axios from limiting the content size
306341
}, ctx.log)
307342
}
308-
343+
309344
processWebFigma(requestBody: any, log: Logger) {
310-
return this.request({
311-
url: "figma-web/upload",
312-
method: "POST",
313-
headers: {
314-
"Content-Type": "application/json",
315-
},
316-
data: JSON.stringify(requestBody)
317-
}, log);
318-
}
345+
return this.request({
346+
url: "figma-web/upload",
347+
method: "POST",
348+
headers: {
349+
"Content-Type": "application/json",
350+
},
351+
data: JSON.stringify(requestBody)
352+
}, log);
353+
}
319354

320355
fetchWebFigma(buildId: any, log: Logger) {
321-
return this.request({
322-
url: "figma-web/fetch",
323-
method: "GET",
324-
headers: {
325-
"Content-Type": "application/json",
326-
},
327-
params: { buildId }
328-
}, log);
329-
}
356+
return this.request({
357+
url: "figma-web/fetch",
358+
method: "GET",
359+
headers: {
360+
"Content-Type": "application/json",
361+
},
362+
params: { buildId }
363+
}, log);
364+
}
330365
}

0 commit comments

Comments
 (0)