Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v4.0.4 #128

Merged
merged 3 commits into from
Sep 17, 2024
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lambdatest/smartui-cli",
"version": "4.0.3",
"version": "4.0.4",
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
"files": [
"dist/**/*"
Expand Down
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ import pkgJSON from './../package.json'
let log = logger;

try {
let { data: { latestVersion, deprecated, additionalDescription, additionalDescriptionLatestVersion } } = await client.checkUpdate(log);
let { data: { latestVersion, deprecated, additionalDescription } } = await client.checkUpdate(log);
log.info(`\nLambdaTest SmartUI CLI v${pkgJSON.version}`);
log.info(chalk.yellow(`${additionalDescription}`));
if (deprecated){
log.warn(`This version is deprecated. A new version ${latestVersion} is available!`);
log.warn(`${additionalDescriptionLatestVersion}\n`);
}
else if (pkgJSON.version !== latestVersion){
log.info(chalk.green(`A new version ${latestVersion} is available!`));
log.info(chalk.red(`${additionalDescriptionLatestVersion}\n`));
}
else log.info(chalk.gray('https://www.npmjs.com/package/@lambdatest/smartui-cli\n'));
} catch (error) {
Expand Down
113 changes: 57 additions & 56 deletions src/lib/processSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class Queue {
private processing: boolean = false;
private processingSnapshot: string = '';
private ctx: Context;

constructor(ctx: Context) {
this.ctx = ctx;
}
Expand All @@ -28,7 +28,7 @@ export default class Queue {
this.processNext();
}
}

private async processNext(): Promise<void> {
if (!this.isEmpty()) {
const snapshot = this.snapshots.shift();
Expand All @@ -37,10 +37,10 @@ export default class Queue {
let { processedSnapshot, warnings } = await processSnapshot(snapshot, this.ctx);
await this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
this.ctx.totalSnapshots++;
this.processedSnapshots.push({name: snapshot.name, warnings});
this.processedSnapshots.push({ name: snapshot.name, warnings });
} catch (error: any) {
this.ctx.log.debug(`snapshot failed; ${error}`);
this.processedSnapshots.push({name: snapshot.name, error: error.message});
this.processedSnapshots.push({ name: snapshot.name, error: error.message });
}
// Close open browser contexts and pages
if (this.ctx.browser) {
Expand Down Expand Up @@ -77,8 +77,8 @@ export default class Queue {
}

async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
updateLogContext({task: 'discovery'});
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
updateLogContext({ task: 'discovery' });
ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);

let launchOptions: Record<string, any> = { headless: true }
let contextOptions: Record<string, any> = {
Expand All @@ -92,35 +92,44 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
}
const context = await ctx.browser.newContext(contextOptions);
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);

// Setting the cookies in playwright context
const domainName = new URL(snapshot.url).hostname;
ctx.log.debug('Setting cookies in context for domain:', domainName);


const cookieArray = snapshot.dom.cookies.split('; ').map(cookie => {
if (!cookie) return null;
const [name, value] = cookie.split('=');
if (!name || !value) return null;

return {
name: name.trim(),
value: value.trim(),
domain: domainName,
path: '/'
};
}).filter(Boolean);


if (cookieArray && Array.isArray(cookieArray) && cookieArray.length > 0) {
await context.addCookies(cookieArray);
ctx.log.debug('Cookies added');
} else {
ctx.log.debug('No valid cookies to add');
if (snapshot.dom.cookies) {
const domainName = new URL(snapshot.url).hostname;
ctx.log.debug(`Setting cookies for domain: ${domainName}`);

const cookieArray = snapshot.dom.cookies.split('; ').map(cookie => {
if (!cookie) return null;
const [name, value] = cookie.split('=');
if (!name || !value) return null;

return {
name: name.trim(),
value: value.trim(),
domain: domainName,
path: '/'
};
}).filter(Boolean);

if (cookieArray.length > 0) {
await context.addCookies(cookieArray);
} else {
ctx.log.debug('No valid cookies to add');
}
}

const page = await context.newPage();

// populate cache with already captured resources
let cache: Record<string, any> = {};
if (snapshot.dom.resources.length) {
for (let resource of snapshot.dom.resources) {
// convert text/css content to base64
let body = resource.mimetype == 'text/css' ? Buffer.from(resource.content).toString('base64') : resource.content;
cache[resource.url] = {
body: body,
type: resource.mimetype
}
}
}

// Use route to intercept network requests and discover resources
await page.route('**/*', async (route, request) => {
Expand All @@ -138,7 +147,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
if (ctx.config.enableJavaScript) ALLOWED_RESOURCES.push('script');
if (ctx.config.basicAuthorization) {
ctx.log.debug(`Adding basic authorization to the headers for root url`);
ctx.log.debug(`Adding basic authorization to the headers for root url`);
let token = Buffer.from(`${ctx.config.basicAuthorization.username}:${ctx.config.basicAuthorization.password}`).toString('base64');
requestOptions.headers = {
...request.headers(),
Expand All @@ -151,9 +160,15 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
if (requestUrl === snapshot.url) {
response = {
status: () => 200,
headers: () => ({ 'content-type': 'text/html' })
headers: () => ({ 'content-type': 'text/html' })
}
body = snapshot.dom.html;
} else if (cache[requestUrl]) {
response = {
status: () => 200,
headers: () => ({ 'content-type': cache[requestUrl].mimetype })
}
body = snapshot.dom.html
body = cache[requestUrl].body;
} else {
response = await page.request.fetch(request, requestOptions);
body = await response.body();
Expand All @@ -162,7 +177,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
// handle response
if (!body) {
ctx.log.debug(`Handling request ${requestUrl}\n - skipping no response`);
} else if (!body.length) {
} else if (!body.length) {
ctx.log.debug(`Handling request ${requestUrl}\n - skipping empty response`);
} else if (requestUrl === snapshot.url) {
ctx.log.debug(`Handling request ${requestUrl}\n - skipping root resource`);
Expand Down Expand Up @@ -204,7 +219,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
let ignoreOrSelectBoxes: string;
if (options && Object.keys(options).length) {
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);

const isNotAllEmpty = (obj: Record<string, Array<string>>): boolean => {
for (let key in obj) if (obj[key]?.length) return true;
return false;
Expand Down Expand Up @@ -240,7 +255,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
selectors.push(...value);
break;
}
}
}
}
}

Expand All @@ -261,14 +276,14 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
// Update `previousDeviceType` to the current device type for comparison in the next iteration
previousDeviceType = device;

await page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
await page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);

// navigate to snapshot url once
if (!navigated) {
try {
// domcontentloaded event is more reliable than load event
await page.goto(snapshot.url, { waitUntil: "domcontentloaded"});
await page.goto(snapshot.url, { waitUntil: "domcontentloaded" });
// adding extra timeout since domcontentloaded event is fired pretty quickly
await new Promise(r => setTimeout(r, 1250));
if (ctx.config.waitForTimeout) await page.waitForTimeout(ctx.config.waitForTimeout);
Expand All @@ -278,7 +293,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
ctx.log.debug(`Navigation to discovery page failed; ${error}`)
throw new Error(error.message)
}

}
if (ctx.config.cliEnableJavaScript && fullPage) await page.evaluate(scrollToBottomAndBackToTop, { frequency: 100, timing: ctx.config.scrollTime });

Expand All @@ -289,8 +304,6 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
ctx.log.debug(`Network idle failed due to ${error}`);
}

await new Promise(r => setTimeout(r, 1000));

// snapshot options
if (processedOptions.element) {
let l = await page.locator(processedOptions.element).all()
Expand Down Expand Up @@ -323,18 +336,6 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
}
}

// add dom resources to cache
if (snapshot.dom.resources.length) {
for (let resource of snapshot.dom.resources) {
// convert text/css content to base64
let body = resource.mimetype == 'text/css' ? Buffer.from(resource.content).toString('base64') : resource.content;
cache[resource.url] = {
body: body,
type: resource.mimetype
}
}
}

return {
processedSnapshot: {
name: snapshot.name,
Expand Down