Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import httpClient from './lib/httpClient.js'
import logger from './lib/logger.js'
import chalk from 'chalk'
import pkgJSON from './../package.json'
import constants from './lib/constants.js';
import fs from 'fs';

(async function() {
let client = new httpClient(getEnv());
let log = logger;

try {
// Delete log file
fs.unlinkSync(constants.LOG_FILE_PATH);
let { data: { latestVersion, deprecated, additionalDescription } } = await client.checkUpdate(log);
log.info(`\nLambdaTest SmartUI CLI v${pkgJSON.version}`);
log.info(chalk.yellow(`${additionalDescription}`));
Expand Down
159 changes: 97 additions & 62 deletions src/lib/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export default class httpClient {

let proxyUrl = null;
try {
// Handle URL with or without protocol
const urlStr = SMARTUI_API_PROXY?.startsWith('http') ?
SMARTUI_API_PROXY : `http://${SMARTUI_API_PROXY}`;
// Handle URL with or without protocol
const urlStr = SMARTUI_API_PROXY?.startsWith('http') ?
SMARTUI_API_PROXY : `http://${SMARTUI_API_PROXY}`;
proxyUrl = SMARTUI_API_PROXY ? new URL(urlStr) : null;
} catch (error) {
console.error('Invalid proxy URL:', error);
Expand All @@ -36,7 +36,7 @@ export default class httpClient {
port: proxyUrl.port ? Number(proxyUrl.port) : 80
} : false
};

if (SMARTUI_API_SKIP_CERTIFICATES) {
axiosConfig.httpsAgent = new https.Agent({
rejectUnauthorized: false
Expand All @@ -45,29 +45,63 @@ export default class httpClient {

this.axiosInstance = axios.create(axiosConfig);


this.axiosInstance.interceptors.request.use((config) => {
config.headers['projectToken'] = this.projectToken;
config.headers['projectName'] = this.projectName;
config.headers['username'] = this.username;
config.headers['accessKey'] = this.accessKey;
return config;
});

// Add a request interceptor for retry logic
this.axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const { config } = error;
if (config && config.url === '/screenshot' && config.method === 'post') {
// Set default retry count and delay if not already defined
if (!config.retryCount) {
config.retryCount = 0;
config.retry = 2;
config.retryDelay = 5000;
}

// Check if we should retry the request
if (config.retryCount < config.retry) {
config.retryCount += 1;
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
config.timeout = 30000;
return this.axiosInstance(config);
}

// If we've reached max retries, reject with the error
return Promise.reject(error);
}
}
);
}



async request(config: AxiosRequestConfig, log: Logger): Promise<Record<string, any>> {
log.debug(`http request: ${config.method} ${config.url}`);
if(config && config.data && !config.data.name) {
if (config && config.data && !config.data.name) {
log.debug(config.data);
}
return this.axiosInstance.request(config)
.then(resp => {
log.debug(`http response: ${JSON.stringify({
status: resp.status,
headers: resp.headers,
body: resp.data
})}`)
return resp.data;
if (resp) {
log.debug(`http response: ${JSON.stringify({
status: resp.status,
headers: resp.headers,
body: resp.data
})}`)
return resp.data;
} else {
log.debug(`empty response: ${JSON.stringify(resp)}`)
return {};
}
})
.catch(error => {
if (error.response) {
Expand Down Expand Up @@ -107,7 +141,7 @@ export default class httpClient {
throw new Error('Authentication failed, project token not received');
}
}

createBuild(git: Git, config: any, log: Logger, buildName: string, isStartExec: boolean) {
return this.request({
url: '/build',
Expand All @@ -128,7 +162,7 @@ export default class httpClient {
params: { buildId, baseline }
}, log);
}

ping(buildId: string, log: Logger) {
return this.request({
url: '/build/ping',
Expand All @@ -138,10 +172,10 @@ export default class httpClient {
}
}, log);
}


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

return this.request({
Expand All @@ -156,7 +190,7 @@ export default class httpClient {
url: `/builds/${ctx.build.id}/snapshot`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: {
data: {
snapshot,
test: {
type: ctx.testType,
Expand All @@ -171,7 +205,7 @@ export default class httpClient {
url: `/build/${ctx.build.id}/snapshot`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: {
data: {
name: snapshot.name,
url: snapshot.url,
snapshotUuid: snapshotUuid,
Expand All @@ -185,13 +219,13 @@ export default class httpClient {
}

uploadScreenshot(
{ id: buildId, name: buildName, baseline }: Build,
ssPath: string, ssName: string, browserName :string, viewport: string, log: Logger
{ id: buildId, name: buildName, baseline }: Build,
ssPath: string, ssName: string, browserName: string, viewport: string, log: Logger
) {
browserName = browserName === constants.SAFARI ? constants.WEBKIT : browserName;
const file = fs.readFileSync(ssPath);
const form = new FormData();
form.append('screenshot', file, { filename: `${ssName}.png`, contentType: 'image/png'});
form.append('screenshot', file, { filename: `${ssName}.png`, contentType: 'image/png' });
form.append('browser', browserName);
form.append('viewport', viewport);
form.append('buildId', buildId);
Expand All @@ -204,19 +238,20 @@ export default class httpClient {
method: 'POST',
headers: form.getHeaders(),
data: form,
timeout: 30000
})
.then(() => {
log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
})
.catch(error => {
log.error(`Unable to upload screenshot ${JSON.stringify(error)}`)
if (error && error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error.message);
}
if (error) {
throw new Error(JSON.stringify(error));
}
})
.then(() => {
log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
})
.catch(error => {
log.error(`Unable to upload screenshot ${JSON.stringify(error)}`)
if (error && error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error.message);
}
if (error) {
throw new Error(JSON.stringify(error));
}
})
}

checkUpdate(log: Logger) {
Expand All @@ -232,15 +267,15 @@ export default class httpClient {
}

getFigmaFilesAndImages(figmaFileToken: string, figmaToken: String | undefined, queryParams: string, authToken: string, depth: number, markBaseline: boolean, buildName: string, log: Logger) {
const requestBody = {
figma_file_token: figmaFileToken,
figma_token: figmaToken,
query_params: queryParams,
auth: authToken,
depth: depth,
mark_base_line: markBaseline,
build_name: buildName
};
const requestBody = {
figma_file_token: figmaFileToken,
figma_token: figmaToken,
query_params: queryParams,
auth: authToken,
depth: depth,
mark_base_line: markBaseline,
build_name: buildName
};

return this.request({
url: "/uploadfigma",
Expand Down Expand Up @@ -297,34 +332,34 @@ export default class httpClient {
return this.request({
url: uploadURL,
method: 'PUT',
headers:{
headers: {
'Content-Type': 'application/json',
},
data: snapshot,
maxBodyLength: Infinity, // prevent axios from limiting the body size
maxContentLength: Infinity, // prevent axios from limiting the content size
}, ctx.log)
}

processWebFigma(requestBody: any, log: Logger) {
return this.request({
url: "figma-web/upload",
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: JSON.stringify(requestBody)
}, log);
}
return this.request({
url: "figma-web/upload",
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: JSON.stringify(requestBody)
}, log);
}

fetchWebFigma(buildId: any, log: Logger) {
return this.request({
url: "figma-web/fetch",
method: "GET",
headers: {
"Content-Type": "application/json",
},
params: { buildId }
}, log);
}
return this.request({
url: "figma-web/fetch",
method: "GET",
headers: {
"Content-Type": "application/json",
},
params: { buildId }
}, log);
}
}
2 changes: 0 additions & 2 deletions src/tasks/finalizeBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
let resp = await ctx.client.getS3PreSignedURL(ctx);
await ctx.client.uploadLogs(ctx, resp.data.url);
}
fs.unlinkSync(constants.LOG_FILE_PATH);
ctx.log.debug(`Log file deleted: ${constants.LOG_FILE_PATH}`);
} catch (error: any) {
ctx.log.debug(error);
}
Expand Down