Skip to content

Commit 3df71ac

Browse files
Merge pull request #101 from LambdaTest/stage
Release v3.0.12
2 parents 290447f + 427eecf commit 3df71ac

File tree

7 files changed

+128
-26
lines changed

7 files changed

+128
-26
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "3.0.11",
3+
"version": "3.0.12",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"

src/commander/exec.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { color, Listr, ListrDefaultRendererLogLevels } from 'listr2'
55
import startServer from '../tasks/startServer.js'
66
import auth from '../tasks/auth.js'
77
import ctxInit from '../lib/ctx.js'
8-
import getGitInfo from '../tasks/getGitInfo.js';
8+
import getGitInfo from '../tasks/getGitInfo.js'
99
import createBuild from '../tasks/createBuild.js'
1010
import exec from '../tasks/exec.js'
11+
import processSnapshots from '../tasks/processSnapshot.js'
1112
import finalizeBuild from '../tasks/finalizeBuild.js'
13+
import snapshotQueue from '../lib/processSnapshot.js'
1214

1315
const command = new Command();
1416

@@ -25,6 +27,7 @@ command
2527
return
2628
}
2729
ctx.args.execCommand = execCommand
30+
ctx.snapshotQueue = new snapshotQueue(ctx)
2831
ctx.totalSnapshots = 0
2932

3033
let tasks = new Listr<Context>(
@@ -34,6 +37,7 @@ command
3437
getGitInfo(ctx),
3538
createBuild(ctx),
3639
exec(ctx),
40+
processSnapshots(ctx),
3741
finalizeBuild(ctx)
3842
],
3943
{
@@ -53,8 +57,10 @@ command
5357
} catch (error) {
5458
ctx.log.info('\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/');
5559
} finally {
56-
await ctx.server?.close();
5760
await ctx.browser?.close();
61+
ctx.log.debug(`Closed browser`);
62+
await ctx.server?.close();
63+
ctx.log.debug(`Closed server`);
5864
}
5965
})
6066

src/lib/httpClient.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,19 @@ export default class httpClient {
7575
}, log)
7676
}
7777

78-
uploadSnapshot(buildId: string, snapshot: ProcessedSnapshot, testType: string, log: Logger) {
78+
uploadSnapshot(ctx: Context, snapshot: ProcessedSnapshot) {
7979
return this.request({
80-
url: `/builds/${buildId}/snapshot`,
80+
url: `/builds/${ctx.build.id}/snapshot`,
8181
method: 'POST',
8282
headers: { 'Content-Type': 'application/json' },
8383
data: {
8484
snapshot,
8585
test: {
86-
type: testType,
86+
type: ctx.testType,
8787
source: 'cli'
8888
}
8989
}
90-
}, log)
90+
}, ctx.log)
9191
}
9292

9393
uploadScreenshot(

src/lib/processSnapshot.ts

+67-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,73 @@ const ALLOWED_STATUSES = [200, 201];
99
const REQUEST_TIMEOUT = 10000;
1010
const MIN_VIEWPORT_HEIGHT = 1080;
1111

12-
export default async (snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> => {
12+
export default class Queue {
13+
private snapshots: Array<Snapshot> = [];
14+
private processedSnapshots: Array<Record<string, any>> = [];
15+
private processing: boolean = false;
16+
private processingSnapshot: string = '';
17+
private ctx: Context;
18+
19+
constructor(ctx: Context) {
20+
this.ctx = ctx;
21+
}
22+
23+
enqueue(item: Snapshot): void {
24+
this.snapshots.push(item);
25+
if (!this.processing) {
26+
this.processing = true;
27+
this.processNext();
28+
}
29+
}
30+
31+
private async processNext(): Promise<void> {
32+
if (!this.isEmpty()) {
33+
const snapshot = this.snapshots.shift();
34+
try {
35+
this.processingSnapshot = snapshot?.name;
36+
let { processedSnapshot, warnings } = await processSnapshot(snapshot, this.ctx);
37+
await this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
38+
this.ctx.totalSnapshots++;
39+
this.processedSnapshots.push({name: snapshot.name, warnings});
40+
} catch (error: any) {
41+
this.ctx.log.debug(`snapshot failed; ${error}`);
42+
this.processedSnapshots.push({name: snapshot.name, error: error.message});
43+
}
44+
// Close open browser contexts and pages
45+
if (this.ctx.browser) {
46+
for (let context of this.ctx.browser.contexts()) {
47+
for (let page of context.pages()) {
48+
await page.close();
49+
this.ctx.log.debug(`Closed browser page for snapshot ${snapshot.name}`);
50+
}
51+
await context.close();
52+
this.ctx.log.debug(`Closed browser context for snapshot ${snapshot.name}`);
53+
}
54+
}
55+
this.processNext();
56+
} else {
57+
this.processing = false;
58+
}
59+
}
60+
61+
isProcessing(): boolean {
62+
return this.processing;
63+
}
64+
65+
getProcessingSnapshot(): string {
66+
return this.processingSnapshot;
67+
}
68+
69+
getProcessedSnapshots(): Array<Record<string, any>> {
70+
return this.processedSnapshots;
71+
}
72+
73+
isEmpty(): boolean {
74+
return this.snapshots.length ? false : true;
75+
}
76+
}
77+
78+
async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
1379
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
1480

1581
let launchOptions: Record<string, any> = { headless: true }

src/lib/server.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,23 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
2323
reply.code(200).send({ data: { dom: SMARTUI_DOM }});
2424
});
2525

26-
// upload snpashot
26+
// process and upload snpashot
2727
server.post('/snapshot', opts, async (request, reply) => {
2828
let replyCode: number;
2929
let replyBody: Record<string, any>;
3030

3131
try {
3232
let { snapshot, testType } = request.body;
3333
if (!validateSnapshot(snapshot)) throw new Error(validateSnapshot.errors[0].message);
34-
let { processedSnapshot, warnings } = await processSnapshot(snapshot, ctx);
35-
await ctx.client.uploadSnapshot(ctx.build.id, processedSnapshot, testType, ctx.log);
36-
ctx.totalSnapshots++;
34+
ctx.testType = testType;
35+
ctx.snapshotQueue?.enqueue(snapshot);
3736
replyCode = 200;
38-
replyBody = { data: { message: "success", warnings }};
37+
replyBody = { data: { message: "success", warnings: [] }};
3938
} catch (error: any) {
4039
ctx.log.debug(`snapshot failed; ${error}`)
4140
replyCode = 500;
4241
replyBody = { error: { message: error.message }}
4342
}
44-
45-
// Close open browser contexts and pages
46-
if (ctx.browser) {
47-
for (let context of ctx.browser.contexts()) {
48-
for (let page of context.pages()) {
49-
await page.close();
50-
ctx.log.debug(`Closed browser page`);
51-
}
52-
await context.close();
53-
ctx.log.debug(`Closed browser context`);
54-
}
55-
}
5643

5744
return reply.code(replyCode).send(replyBody);
5845
});

src/tasks/processSnapshot.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ListrTask, ListrRendererFactory } from 'listr2';
2+
import { Context } from '../types.js'
3+
import chalk from 'chalk';
4+
import { updateLogContext } from '../lib/logger.js';
5+
6+
export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRendererFactory> => {
7+
return {
8+
title: `Processing snapshots`,
9+
task: async (ctx, task): Promise<void> => {
10+
11+
try {
12+
// wait for snapshot queue to be empty
13+
await new Promise((resolve) => {
14+
let output: string = '';
15+
const intervalId = setInterval(() => {
16+
if (ctx.snapshotQueue?.isEmpty() && !ctx.snapshotQueue?.isProcessing()) {
17+
clearInterval(intervalId);
18+
resolve();
19+
} else {
20+
task.title = `Processing snapshot ${ctx.snapshotQueue?.getProcessingSnapshot()}`
21+
}
22+
}, 500);
23+
})
24+
25+
let output = '';
26+
for (let snapshot of ctx.snapshotQueue?.getProcessedSnapshots()) {
27+
if (snapshot.error) output += `${chalk.red('\u{2717}')} ${chalk.gray(`${snapshot.name}\n[error] ${snapshot.error}`)}\n`;
28+
else output += `${chalk.green('\u{2713}')} ${chalk.gray(snapshot.name)}\n${snapshot.warnings.length ? chalk.gray(`[warning] ${snapshot.warnings.join('\n[warning] ')}\n`) : ''}`;
29+
}
30+
task.output = output;
31+
task.title = 'Processed snapshots'
32+
} catch (error: any) {
33+
ctx.log.debug(error);
34+
task.output = chalk.gray(error.message);
35+
throw new Error('Processing of snapshots failed');
36+
}
37+
},
38+
rendererOptions: { persistentOutput: true }
39+
}
40+
}

src/types.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import httpClient from './lib/httpClient.js'
44
import type { Logger } from 'winston'
55
import { ListrTaskWrapper, ListrRenderer } from "listr2";
66
import { Browser } from '@playwright/test';
7+
import snapshotQueue from './lib/processSnapshot.js';
78

89
export interface Context {
910
env: Env;
@@ -12,6 +13,7 @@ export interface Context {
1213
server?: FastifyInstance<Server, IncomingMessage, ServerResponse>;
1314
client: httpClient;
1415
browser?: Browser;
16+
snapshotQueue?: snapshotQueue;
1517
config: {
1618
web?: WebConfig;
1719
mobile?: MobileConfig,
@@ -34,7 +36,8 @@ export interface Context {
3436
}
3537
cliVersion: string;
3638
totalSnapshots: number;
37-
figmaDesignConfig: FigmaDesignConfig;
39+
figmaDesignConfig?: FigmaDesignConfig;
40+
testType?: string;
3841
}
3942

4043
export interface Env {

0 commit comments

Comments
 (0)