Skip to content

Commit d7902fa

Browse files
Merge pull request #434 from parthlambdatest/Dot-6807
[Dot-6807] Add support for rendering errors for static approach
2 parents 8404e85 + be6bb9c commit d7902fa

File tree

4 files changed

+138
-31
lines changed

4 files changed

+138
-31
lines changed

src/lib/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,6 @@ export default (): Env => {
6161
LT_SDK_SKIP_EXECUTION_LOGS: LT_SDK_SKIP_EXECUTION_LOGS === 'true',
6262
MAX_CONCURRENT_PROCESSING: MAX_CONCURRENT_PROCESSING ? parseInt(MAX_CONCURRENT_PROCESSING, 10) : 0,
6363
DO_NOT_USE_USER_AGENT: DO_NOT_USE_USER_AGENT === 'true',
64+
CAPTURE_RENDERING_ERRORS: process.env.CAPTURE_RENDERING_ERRORS === 'true',
6465
}
6566
}

src/lib/httpClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ export default class httpClient {
457457

458458
uploadScreenshot(
459459
{ id: buildId, name: buildName, baseline }: Build,
460-
ssPath: string, ssName: string, browserName: string, viewport: string, url: string = '', log: Logger
460+
ssPath: string, ssName: string, browserName: string, viewport: string, url: string = '', log: Logger, discoveryErrors?: DiscoveryErrors, ctx?: Context
461461
) {
462462
browserName = browserName === constants.SAFARI ? constants.WEBKIT : browserName;
463463
const file = fs.readFileSync(ssPath);
@@ -470,6 +470,9 @@ export default class httpClient {
470470
form.append('screenshotName', ssName);
471471
form.append('baseline', baseline.toString());
472472
form.append('pageUrl',url)
473+
if (ctx?.env.CAPTURE_RENDERING_ERRORS && discoveryErrors) {
474+
form.append('discoveryErrors', JSON.stringify(discoveryErrors));
475+
}
473476

474477
return this.axiosInstance.request({
475478
url: `/screenshot`,

src/lib/screenshot.ts

Lines changed: 132 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33
import { Browser, BrowserContext, Page } from "@playwright/test"
4-
import { Context } from "../types.js"
4+
import { Context, DiscoveryErrors } from "../types.js"
55
import * as utils from "./utils.js"
66
import constants from './constants.js'
77
import chalk from 'chalk';
@@ -10,9 +10,9 @@ import sharp from 'sharp';
1010
async function captureScreenshotsForConfig(
1111
ctx: Context,
1212
browsers: Record<string, Browser>,
13-
urlConfig : Record<string, any>,
13+
urlConfig: Record<string, any>,
1414
browserName: string,
15-
renderViewports: Array<Record<string,any>>
15+
renderViewports: Array<Record<string, any>>
1616
): Promise<void> {
1717
ctx.log.debug(`*** urlConfig ${JSON.stringify(urlConfig)}`);
1818

@@ -22,7 +22,18 @@ async function captureScreenshotsForConfig(
2222
let beforeSnapshotScript = execute?.beforeSnapshot;
2323
let waitUntilEvent = pageEvent || process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || 'load';
2424

25-
let pageOptions = { waitUntil: waitUntilEvent, timeout: ctx.config.waitForPageRender || constants.DEFAULT_PAGE_LOAD_TIMEOUT};
25+
let discoveryErrors: DiscoveryErrors = {
26+
name: "",
27+
url: "",
28+
timestamp: "",
29+
snapshotUUID: "",
30+
browsers: {}
31+
};
32+
33+
let globalViewport = ""
34+
let globalBrowser = constants.CHROME
35+
36+
let pageOptions = { waitUntil: waitUntilEvent, timeout: ctx.config.waitForPageRender || constants.DEFAULT_PAGE_LOAD_TIMEOUT };
2637
ctx.log.debug(`url: ${url} pageOptions: ${JSON.stringify(pageOptions)}`);
2738
let ssId = name.toLowerCase().replace(/\s/g, '_');
2839
let context: BrowserContext;
@@ -106,8 +117,8 @@ async function captureScreenshotsForConfig(
106117
else if (browserName == constants.SAFARI) contextOptions.userAgent = constants.SAFARI_USER_AGENT;
107118
else if (browserName == constants.EDGE) contextOptions.userAgent = constants.EDGE_USER_AGENT;
108119
if (ctx.config.userAgent || userAgent) {
109-
if(ctx.config.userAgent !== ""){
110-
contextOptions.userAgent = ctx.config.userAgent;
120+
if (ctx.config.userAgent !== "") {
121+
contextOptions.userAgent = ctx.config.userAgent;
111122
}
112123
if (userAgent && userAgent !== "") {
113124
contextOptions.userAgent = userAgent;
@@ -155,27 +166,112 @@ async function captureScreenshotsForConfig(
155166
await page.setExtraHTTPHeaders(headersObject);
156167
}
157168

169+
170+
if (ctx.env.CAPTURE_RENDERING_ERRORS) {
171+
await page.route('**/*', async (route, request) => {
172+
const requestUrl = request.url()
173+
const requestHostname = new URL(requestUrl).hostname;
174+
let requestOptions: Record<string, any> = {
175+
timeout: 30000,
176+
headers: {
177+
...await request.allHeaders(),
178+
...constants.REQUEST_HEADERS
179+
}
180+
}
181+
182+
try {
183+
184+
// get response
185+
let response, body;
186+
response = await page.request.fetch(request, requestOptions);
187+
body = await response.body();
188+
189+
let data = {
190+
statusCode: `${response.status()}`,
191+
url: requestUrl,
192+
}
193+
194+
if ((response.status() >= 400 && response.status() < 600) && response.status() !== 0) {
195+
if (!discoveryErrors.browsers[globalBrowser]) {
196+
discoveryErrors.browsers[globalBrowser] = {};
197+
}
198+
199+
// Check if the discoveryErrors.browsers[globalBrowser] exists, and if not, initialize it
200+
if (discoveryErrors.browsers[globalBrowser] && !discoveryErrors.browsers[globalBrowser][globalViewport]) {
201+
discoveryErrors.browsers[globalBrowser][globalViewport] = [];
202+
}
203+
204+
// Dynamically push the data into the correct browser and viewport
205+
if (discoveryErrors.browsers[globalBrowser]) {
206+
discoveryErrors.browsers[globalBrowser][globalViewport]?.push(data as any);
207+
}
208+
209+
ctx.build.hasDiscoveryError = true;
210+
}
211+
212+
// Continue the request with the fetched response
213+
route.fulfill({
214+
status: response.status(),
215+
headers: response.headers(),
216+
body: body,
217+
});
218+
} catch (error: any) {
219+
ctx.log.debug(`Handling request ${requestUrl}\n - aborted due to ${error.message}`);
220+
route.abort();
221+
}
222+
});
223+
}
224+
225+
if (renderViewports && renderViewports.length > 0) {
226+
const first = renderViewports[0];
227+
globalViewport = first.viewportString;
228+
globalBrowser = browserName;
229+
if (globalViewport.toLowerCase().includes("iphone") || globalViewport.toLowerCase().includes("ipad")) {
230+
globalBrowser = constants.WEBKIT;
231+
}
232+
}
233+
234+
if (browserName == constants.SAFARI || (globalViewport.toLowerCase().includes("iphone") || globalViewport.toLowerCase().includes("ipad"))) {
235+
globalBrowser = constants.WEBKIT;
236+
}
237+
158238
await page?.goto(url.trim(), pageOptions);
159239
await executeDocumentScripts(ctx, page, "afterNavigation", afterNavigationScript)
160240

161241
for (let { viewport, viewportString, fullPage } of renderViewports) {
242+
globalViewport = viewportString;
243+
globalBrowser = browserName
244+
ctx.log.debug(`globalViewport : ${globalViewport}`);
245+
if (browserName == constants.SAFARI || (globalViewport.toLowerCase().includes("iphone") || globalViewport.toLowerCase().includes("ipad"))) {
246+
globalBrowser = constants.WEBKIT;
247+
}
162248
let ssPath = `screenshots/${ssId}/${`${browserName}-${viewport.width}x${viewport.height}`}-${ssId}.png`;
163249
await page?.setViewportSize({ width: viewport.width, height: viewport.height || constants.MIN_VIEWPORT_HEIGHT });
164250
if (fullPage) await page?.evaluate(utils.scrollToBottomAndBackToTop);
165251
await page?.waitForTimeout(waitForTimeout || 0);
166252
await executeDocumentScripts(ctx, page, "beforeSnapshot", beforeSnapshotScript)
167253

254+
discoveryErrors.name = name;
255+
discoveryErrors.url = url;
256+
discoveryErrors.timestamp = new Date().toISOString();
168257
await page?.screenshot({ path: ssPath, fullPage });
169258

170-
await ctx.client.uploadScreenshot(ctx.build, ssPath, name, browserName, viewportString, url, ctx.log);
259+
await ctx.client.uploadScreenshot(ctx.build, ssPath, name, browserName, viewportString, url, ctx.log, discoveryErrors, ctx);
260+
discoveryErrors = {
261+
name: "",
262+
url: "",
263+
timestamp: "",
264+
snapshotUUID: "",
265+
browsers: {}
266+
};
171267
}
172268
} catch (error) {
173269
throw new Error(`captureScreenshotsForConfig failed for browser ${browserName}; error: ${error}`);
174270
} finally {
175271
await page?.close();
176272
await context?.close();
177273
}
178-
274+
179275
}
180276

181277
async function captureScreenshotsAsync(
@@ -185,8 +281,8 @@ async function captureScreenshotsAsync(
185281
): Promise<void[]> {
186282
let capturePromises: Array<Promise<void>> = [];
187283

188-
// capture screenshots for web config
189-
if (ctx.config.web) {
284+
// capture screenshots for web config
285+
if (ctx.config.web) {
190286
for (let browserName of ctx.config.web.browsers) {
191287
let webRenderViewports = utils.getWebRenderViewports(ctx);
192288
capturePromises.push(captureScreenshotsForConfig(ctx, browsers, staticConfig, browserName, webRenderViewports))
@@ -211,8 +307,8 @@ async function captureScreenshotsSync(
211307
staticConfig: Record<string, any>,
212308
browsers: Record<string, Browser>
213309
): Promise<void> {
214-
// capture screenshots for web config
215-
if (ctx.config.web) {
310+
// capture screenshots for web config
311+
if (ctx.config.web) {
216312
for (let browserName of ctx.config.web.browsers) {
217313
let webRenderViewports = utils.getWebRenderViewports(ctx);
218314
await captureScreenshotsForConfig(ctx, browsers, staticConfig, browserName, webRenderViewports);
@@ -230,11 +326,11 @@ async function captureScreenshotsSync(
230326
}
231327
}
232328

233-
export async function captureScreenshots(ctx: Context): Promise<Record<string,any>> {
329+
export async function captureScreenshots(ctx: Context): Promise<Record<string, any>> {
234330
// Clean up directory to store screenshots
235331
utils.delDir('screenshots');
236332

237-
let browsers: Record<string,Browser> = {};
333+
let browsers: Record<string, Browser> = {};
238334
let capturedScreenshots: number = 0;
239335
let output: string = '';
240336

@@ -363,7 +459,13 @@ export async function uploadScreenshots(ctx: Context): Promise<void> {
363459
}
364460
}
365461

366-
await ctx.client.uploadScreenshot(ctx.build, filePath, ssId, 'default', viewport,"", ctx.log);
462+
await ctx.client.uploadScreenshot(ctx.build, filePath, ssId, 'default', viewport, "", ctx.log, {
463+
name: "",
464+
url: "",
465+
timestamp: new Date().toISOString(),
466+
snapshotUUID: "",
467+
browsers: {}
468+
}, ctx);
367469
ctx.log.info(`${filePath} : uploaded successfully`)
368470
noOfScreenshots++;
369471
} else {
@@ -374,20 +476,20 @@ export async function uploadScreenshots(ctx: Context): Promise<void> {
374476
}
375477

376478
await processDirectory(ctx.uploadFilePath);
377-
if(noOfScreenshots == 0){
479+
if (noOfScreenshots == 0) {
378480
ctx.log.info(`No screenshots uploaded.`);
379481
} else {
380482
ctx.log.info(`${noOfScreenshots} screenshots uploaded successfully.`);
381483
}
382484
}
383485

384-
export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record<string,any>> {
486+
export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record<string, any>> {
385487
// Clean up directory to store screenshots
386488
utils.delDir('screenshots');
387489

388490
let totalSnapshots = ctx.webStaticConfig && ctx.webStaticConfig.length;
389491
let browserInstances = ctx.options.parallel || 1;
390-
let optimizeBrowserInstances : number = 0
492+
let optimizeBrowserInstances: number = 0
391493
optimizeBrowserInstances = Math.floor(Math.log2(totalSnapshots));
392494
if (optimizeBrowserInstances < 1) {
393495
optimizeBrowserInstances = 1;
@@ -398,11 +500,11 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
398500
}
399501

400502
// If force flag is set, use the requested browser instances
401-
if (ctx.options.force && browserInstances > 1){
503+
if (ctx.options.force && browserInstances > 1) {
402504
optimizeBrowserInstances = browserInstances;
403505
}
404506

405-
let urlsPerInstance : number = 0;
507+
let urlsPerInstance: number = 0;
406508
if (optimizeBrowserInstances == 1) {
407509
urlsPerInstance = totalSnapshots;
408510
} else {
@@ -418,9 +520,9 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
418520
let output: any = '';
419521

420522
const responses = await Promise.all(staticURLChunks.map(async (urlConfig) => {
421-
let { capturedScreenshots, finalOutput} = await processChunk(ctx, urlConfig);
523+
let { capturedScreenshots, finalOutput } = await processChunk(ctx, urlConfig);
422524
return { capturedScreenshots, finalOutput };
423-
}));
525+
}));
424526

425527
responses.forEach((response: Record<string, any>) => {
426528
totalCapturedScreenshots += response.capturedScreenshots;
@@ -432,17 +534,17 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
432534
return { totalCapturedScreenshots, output };
433535
}
434536

435-
function splitURLs(arr : any, chunkSize : number) {
537+
function splitURLs(arr: any, chunkSize: number) {
436538
const result = [];
437539
for (let i = 0; i < arr.length; i += chunkSize) {
438-
result.push(arr.slice(i, i + chunkSize));
540+
result.push(arr.slice(i, i + chunkSize));
439541
}
440542
return result;
441543
}
442544

443-
async function processChunk(ctx: Context, urlConfig: Array<Record<string, any>>): Promise<Record<string,any>> {
444-
445-
let browsers: Record<string,Browser> = {};
545+
async function processChunk(ctx: Context, urlConfig: Array<Record<string, any>>): Promise<Record<string, any>> {
546+
547+
let browsers: Record<string, Browser> = {};
446548
let capturedScreenshots: number = 0;
447549
let finalOutput: string = '';
448550

@@ -454,13 +556,13 @@ async function processChunk(ctx: Context, urlConfig: Array<Record<string, any>>)
454556
throw new Error(`Failed launching browsers ${error}`);
455557
}
456558

457-
for (let staticConfig of urlConfig) {
559+
for (let staticConfig of urlConfig) {
458560
try {
459561
await captureScreenshotsAsync(ctx, staticConfig, browsers);
460562

461563
utils.delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, '_')}`);
462564
let output = (`${chalk.gray(staticConfig.name)} ${chalk.green('\u{2713}')}\n`);
463-
ctx.task.output = ctx.task.output? ctx.task.output +output : output;
565+
ctx.task.output = ctx.task.output ? ctx.task.output + output : output;
464566
finalOutput += output;
465567
capturedScreenshots++;
466568
} catch (error) {
@@ -488,6 +590,6 @@ async function executeDocumentScripts(ctx: Context, page: Page, actionType: stri
488590
}
489591
} catch (error) {
490592
ctx.log.error(`Error executing script for action ${actionType}: `, error);
491-
throw error;
593+
throw error;
492594
}
493595
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export interface Env {
141141
LT_SDK_SKIP_EXECUTION_LOGS: boolean;
142142
MAX_CONCURRENT_PROCESSING: number;
143143
DO_NOT_USE_USER_AGENT: boolean;
144+
CAPTURE_RENDERING_ERRORS: boolean;
144145
}
145146

146147
export interface Snapshot {

0 commit comments

Comments
 (0)