Skip to content

Commit a560ebe

Browse files
committed
fix capture screenshot with parallel flag
1 parent b30928e commit a560ebe

File tree

6 files changed

+124
-14
lines changed

6 files changed

+124
-14
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
],
88
"scripts": {
99
"build": "tsup",
10-
"release": "pnpm run build && pnpm publish --access public --no-git-checks"
10+
"release": "pnpm run build && pnpm publish --access public --no-git-checks",
11+
"local-build": "pnpm run build && pnpm pack"
1112
},
1213
"bin": {
1314
"smartui": "./dist/index.cjs"

src/commander/capture.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@ command
1616
.name('capture')
1717
.description('Capture screenshots of static sites')
1818
.argument('<file>', 'Web static config file')
19-
.option('--parallel', 'Capture parallely on all browsers')
19+
.option('-P, --parallel [number]', 'Specify the number of instances per browser', parseInt)
20+
.option('-F, --force', 'forcefully apply the specified parallel instances per browser')
2021
.action(async function(file, _, command) {
2122
let ctx: Context = ctxInit(command.optsWithGlobals());
22-
23+
2324
if (!fs.existsSync(file)) {
2425
console.log(`Error: Web Static Config file ${file} not found.`);
2526
return;
2627
}
2728
try {
2829
ctx.webStaticConfig = JSON.parse(fs.readFileSync(file, 'utf8'));
2930
if (!validateWebStaticConfig(ctx.webStaticConfig)) throw new Error(validateWebStaticConfig.errors[0].message);
31+
if(ctx.webStaticConfig && ctx.webStaticConfig.length === 0) {
32+
console.log(`[smartui] Error: No URLs found in the specified config file -> ${file}`);
33+
return;
34+
}
3035
} catch (error: any) {
3136
console.log(`[smartui] Error: Invalid Web Static Config; ${error.message}`);
3237
return;

src/lib/ctx.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default (options: Record<string, string>): Context => {
1818
let extensionFiles: string;
1919
let ignoreStripExtension: Array<string>;
2020
let ignoreFilePattern: Array<string>;
21+
let parallelObj: number;
2122
try {
2223
if (options.config) {
2324
config = JSON.parse(fs.readFileSync(options.config, 'utf-8'));
@@ -41,6 +42,8 @@ export default (options: Record<string, string>): Context => {
4142
extensionFiles = options.files || ['png', 'jpeg', 'jpg'];
4243
ignoreStripExtension = options.removeExtensions || false
4344
ignoreFilePattern = options.ignoreDir || []
45+
46+
parallelObj = options.parallel ? options.parallel === true? 1 : options.parallel: -1;
4447
} catch (error: any) {
4548
console.log(`[smartui] Error: ${error.message}`);
4649
process.exit();
@@ -59,7 +62,7 @@ export default (options: Record<string, string>): Context => {
5962
}
6063
if (config.basicAuthorization) {
6164
basicAuthObj = config.basicAuthorization
62-
}
65+
}
6366

6467
return {
6568
env: env,
@@ -95,7 +98,8 @@ export default (options: Record<string, string>): Context => {
9598
},
9699
args: {},
97100
options: {
98-
parallel: options.parallel ? true : false,
101+
parallel: parallelObj,
102+
force: options.force ? true : false,
99103
markBaseline: options.markBaseline ? true : false,
100104
buildName: options.buildName || '',
101105
port: port,

src/lib/screenshot.ts

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@ async function captureScreenshotsSync(
103103
export async function captureScreenshots(ctx: Context): Promise<Record<string,any>> {
104104
// Clean up directory to store screenshots
105105
utils.delDir('screenshots');
106-
console.log(`ctx.options.parallel ${ctx.options.parallel} `);
107-
108-
// Check Parallel Option and divide web static config accordingly
109-
console.log(`Capturing screenshots for ${ctx.webStaticConfig.length} URLs`);
110106

111107
let browsers: Record<string,Browser> = {};
112108
let capturedScreenshots: number = 0;
@@ -254,3 +250,98 @@ export async function uploadScreenshots(ctx: Context): Promise<void> {
254250
ctx.log.info(`${noOfScreenshots} screenshots uploaded successfully.`);
255251
}
256252
}
253+
254+
export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record<string,any>> {
255+
// Clean up directory to store screenshots
256+
utils.delDir('screenshots');
257+
258+
let totalSnapshots = ctx.webStaticConfig && ctx.webStaticConfig.length;
259+
let browserInstances = ctx.options.parallel || 1;
260+
let optimizeBrowserInstances : number = 0
261+
optimizeBrowserInstances = Math.floor(Math.log2(totalSnapshots));
262+
if (browserInstances != -1 && optimizeBrowserInstances > browserInstances) {
263+
optimizeBrowserInstances = browserInstances;
264+
}
265+
266+
// If force flag is set, use the requested browser instances
267+
if (ctx.options.force && browserInstances > 1){
268+
optimizeBrowserInstances = browserInstances;
269+
}
270+
271+
let urlsPerInstance : number = 0;
272+
if (optimizeBrowserInstances == 1) {
273+
urlsPerInstance = totalSnapshots;
274+
} else {
275+
urlsPerInstance = Math.ceil(totalSnapshots / optimizeBrowserInstances);
276+
}
277+
console.log(`*** browserInstances requested ${ctx.options.parallel} `);
278+
console.log(`*** optimizeBrowserInstances ${optimizeBrowserInstances} `);
279+
console.log(`*** urlsPerInstance ${urlsPerInstance} = ${totalSnapshots} / ${optimizeBrowserInstances}`);
280+
ctx.task.output = `Parallel Browser Instances: ${optimizeBrowserInstances}\n`;
281+
//Divide the URLs into chunks
282+
let staticURLChunks = splitURLs(ctx.webStaticConfig, urlsPerInstance);
283+
let totalCapturedScreenshots: number = 0;
284+
let output: any = '';
285+
286+
const responses = await Promise.all(staticURLChunks.map(async (urlConfig) => {
287+
console.log(`@@@@ staticURLChunks ${JSON.stringify(urlConfig)}`);
288+
let { capturedScreenshots, finalOutput} = await processChunk(ctx, urlConfig);
289+
return { capturedScreenshots, finalOutput };
290+
}));
291+
292+
console.log(`*** responses ${JSON.stringify(responses)}`);
293+
294+
responses.forEach((response: Record<string, any>) => {
295+
totalCapturedScreenshots += response.capturedScreenshots;
296+
output += response.finalOutput;
297+
});
298+
console.log(`*** totalCapturedScreenshots ${totalCapturedScreenshots}`);
299+
300+
utils.delDir('screenshots');
301+
302+
return { totalCapturedScreenshots, output };
303+
}
304+
305+
function splitURLs(arr : any, chunkSize : number) {
306+
const result = [];
307+
for (let i = 0; i < arr.length; i += chunkSize) {
308+
result.push(arr.slice(i, i + chunkSize));
309+
}
310+
return result;
311+
}
312+
313+
async function processChunk(ctx: Context, urlConfig: Array<Record<string, any>>): Promise<Record<string,any>> {
314+
315+
let browsers: Record<string,Browser> = {};
316+
let capturedScreenshots: number = 0;
317+
let finalOutput: string = '';
318+
319+
try {
320+
browsers = await utils.launchBrowsers(ctx);
321+
} catch (error) {
322+
await utils.closeBrowsers(browsers);
323+
ctx.log.debug(error)
324+
throw new Error(`Failed launching browsers ${error}`);
325+
}
326+
327+
for (let staticConfig of urlConfig) {
328+
try {
329+
console.log(`#### staticConfig ${JSON.stringify(staticConfig)}`);
330+
await captureScreenshotsAsync(ctx, staticConfig, browsers);
331+
332+
utils.delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, '_')}`);
333+
let output = (`${chalk.gray(staticConfig.name)} ${chalk.green('\u{2713}')}\n`);
334+
ctx.task.output = ctx.task.output? ctx.task.output +output : output;
335+
finalOutput += output;
336+
capturedScreenshots++;
337+
} catch (error) {
338+
ctx.log.debug(`screenshot capture failed for ${JSON.stringify(staticConfig)}; error: ${error}`);
339+
let output = `${chalk.gray(staticConfig.name)} ${chalk.red('\u{2717}')}\n`;
340+
ctx.task.output += output;
341+
finalOutput += output;
342+
}
343+
}
344+
345+
await utils.closeBrowsers(browsers);
346+
return { capturedScreenshots, finalOutput };
347+
}

src/tasks/captureScreenshots.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ListrTask, ListrRendererFactory } from 'listr2';
22
import { Context } from '../types.js'
3-
import { captureScreenshots } from '../lib/screenshot.js'
3+
import { captureScreenshots, captureScreenshotsConcurrent } from '../lib/screenshot.js'
44
import chalk from 'chalk';
55
import { updateLogContext } from '../lib/logger.js'
66

@@ -12,9 +12,17 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1212
ctx.task = task;
1313
updateLogContext({task: 'capture'});
1414

15-
let { capturedScreenshots, output } = await captureScreenshots(ctx);
16-
if (capturedScreenshots != ctx.webStaticConfig.length) {
17-
throw new Error(output)
15+
if (ctx.options.parallel) {
16+
let { totalCapturedScreenshots, output } = await captureScreenshotsConcurrent(ctx);
17+
console.log(`#### captureScreenshotsConcurrent ${totalCapturedScreenshots} ${output}`);
18+
if (totalCapturedScreenshots != ctx.webStaticConfig.length) {
19+
throw new Error(output)
20+
}
21+
} else {
22+
let { capturedScreenshots, output } = await captureScreenshots(ctx);
23+
if (capturedScreenshots != ctx.webStaticConfig.length) {
24+
throw new Error(output)
25+
}
1826
}
1927
task.title = 'Screenshots captured successfully'
2028
} catch (error: any) {

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export interface Context {
3535
execCommand?: Array<string>
3636
}
3737
options: {
38-
parallel?: boolean,
38+
parallel?: number,
39+
force?: boolean,
3940
markBaseline?: boolean,
4041
buildName?: string,
4142
port?: number,

0 commit comments

Comments
 (0)