Skip to content

Commit 59538f4

Browse files
committed
Add remote discovery support for snapshot processing
1 parent 4669ec4 commit 59538f4

File tree

5 files changed

+184
-8
lines changed

5 files changed

+184
-8
lines changed

src/lib/env.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export default (): Env => {
1919
CURRENT_BRANCH,
2020
PROJECT_NAME,
2121
SMARTUI_API_PROXY,
22-
SMARTUI_API_SKIP_CERTIFICATES
22+
SMARTUI_API_SKIP_CERTIFICATES,
23+
USE_REMOTE_DISCOVERY
2324
} = process.env
2425

2526
return {
@@ -40,6 +41,7 @@ export default (): Env => {
4041
SMARTUI_DO_NOT_USE_CAPTURED_COOKIES: SMARTUI_DO_NOT_USE_CAPTURED_COOKIES === 'true',
4142
PROJECT_NAME,
4243
SMARTUI_API_PROXY,
43-
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true'
44+
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true',
45+
USE_REMOTE_DISCOVERY: USE_REMOTE_DISCOVERY === 'true'
4446
}
4547
}

src/lib/httpClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export default class httpClient {
271271
type: ctx.testType,
272272
source: 'cli'
273273
},
274-
async: false,
274+
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
275275
discoveryErrors: discoveryErrors,
276276
}
277277
}, ctx.log)
@@ -293,7 +293,7 @@ export default class httpClient {
293293
type: ctx.testType,
294294
source: 'cli'
295295
},
296-
async: false,
296+
doRemoteDiscovery: snapshot.options.doRemoteDiscovery,
297297
discoveryErrors: discoveryErrors,
298298
}
299299
}, ctx.log)

src/lib/processSnapshot.ts

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,162 @@ const ALLOWED_STATUSES = [200, 201];
1212
const REQUEST_TIMEOUT = 1800000;
1313
const MIN_VIEWPORT_HEIGHT = 1080;
1414

15+
export async function prepareSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
16+
let processedOptions: Record<string, any> = {};
17+
processedOptions.cliEnableJavascript = ctx.config.cliEnableJavaScript;
18+
processedOptions.ignoreHTTPSErrors = ctx.config.ignoreHTTPSErrors;
19+
if (ctx.config.basicAuthorization) {
20+
processedOptions.basicAuthorization = ctx.config.basicAuthorization;
21+
}
22+
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
23+
processedOptions.allowedHostnames = ctx.config.allowedHostnames;
24+
processedOptions.skipCapturedCookies = ctx.env.SMARTUI_DO_NOT_USE_CAPTURED_COOKIES;
25+
26+
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY) processedOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
27+
if (ctx.env.SMARTUI_HTTP_PROXY || ctx.env.SMARTUI_HTTPS_PROXY) processedOptions.proxy = { server: ctx.env.SMARTUI_HTTP_PROXY || ctx.env.SMARTUI_HTTPS_PROXY };
28+
29+
let options = snapshot.options;
30+
let optionWarnings: Set<string> = new Set();
31+
let selectors: Array<string> = [];
32+
let ignoreOrSelectDOM: string;
33+
let ignoreOrSelectBoxes: string;
34+
35+
if (options && Object.keys(options).length) {
36+
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
37+
38+
const isNotAllEmpty = (obj: Record<string, Array<string>>): boolean => {
39+
for (let key in obj) if (obj[key]?.length) return true;
40+
return false;
41+
}
42+
43+
if (options.loadDomContent) {
44+
processedOptions.loadDomContent = true;
45+
}
46+
47+
if (options.sessionId) {
48+
const sessionId = options.sessionId;
49+
processedOptions.sessionId = sessionId
50+
if (ctx.sessionCapabilitiesMap && ctx.sessionCapabilitiesMap.has(sessionId)) {
51+
const sessionCapabilities = ctx.sessionCapabilitiesMap.get(sessionId);
52+
if (sessionCapabilities && sessionCapabilities.id) {
53+
processedOptions.testId = sessionCapabilities.id;
54+
}
55+
}
56+
}
57+
58+
if (options.web && Object.keys(options.web).length) {
59+
processedOptions.web = {};
60+
61+
// Check and process viewports in web
62+
if (options.web.viewports && options.web.viewports.length > 0) {
63+
processedOptions.web.viewports = options.web.viewports.filter(viewport =>
64+
Array.isArray(viewport) && viewport.length > 0
65+
);
66+
}
67+
68+
// Check and process browsers in web
69+
if (options.web.browsers && options.web.browsers.length > 0) {
70+
processedOptions.web.browsers = options.web.browsers;
71+
}
72+
}
73+
74+
if (options.mobile && Object.keys(options.mobile).length) {
75+
processedOptions.mobile = {};
76+
77+
// Check and process devices in mobile
78+
if (options.mobile.devices && options.mobile.devices.length > 0) {
79+
processedOptions.mobile.devices = options.mobile.devices;
80+
}
81+
82+
// Check if 'fullPage' is provided and is a boolean, otherwise set default to true
83+
if (options.mobile.hasOwnProperty('fullPage') && typeof options.mobile.fullPage === 'boolean') {
84+
processedOptions.mobile.fullPage = options.mobile.fullPage;
85+
} else {
86+
processedOptions.mobile.fullPage = true; // Default value for fullPage
87+
}
88+
89+
// Check if 'orientation' is provided and is valid, otherwise set default to 'portrait'
90+
if (options.mobile.hasOwnProperty('orientation') && (options.mobile.orientation === constants.MOBILE_ORIENTATION_PORTRAIT || options.mobile.orientation === constants.MOBILE_ORIENTATION_LANDSCAPE)) {
91+
processedOptions.mobile.orientation = options.mobile.orientation;
92+
} else {
93+
processedOptions.mobile.orientation = constants.MOBILE_ORIENTATION_PORTRAIT; // Default value for orientation
94+
}
95+
}
96+
97+
if (options.element && Object.keys(options.element).length) {
98+
if (options.element.id) processedOptions.element = '#' + options.element.id;
99+
else if (options.element.class) processedOptions.element = '.' + options.element.class;
100+
else if (options.element.cssSelector) processedOptions.element = options.element.cssSelector;
101+
else if (options.element.xpath) processedOptions.element = 'xpath=' + options.element.xpath;
102+
} else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
103+
processedOptions.ignoreBoxes = {};
104+
ignoreOrSelectDOM = 'ignoreDOM';
105+
ignoreOrSelectBoxes = 'ignoreBoxes';
106+
} else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
107+
processedOptions.selectBoxes = {};
108+
ignoreOrSelectDOM = 'selectDOM';
109+
ignoreOrSelectBoxes = 'selectBoxes';
110+
}
111+
if (ignoreOrSelectDOM) {
112+
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
113+
switch (key) {
114+
case 'id':
115+
selectors.push(...value.map(e => '#' + e));
116+
break;
117+
case 'class':
118+
selectors.push(...value.map(e => '.' + e));
119+
break;
120+
case 'xpath':
121+
selectors.push(...value.map(e => 'xpath=' + e));
122+
break;
123+
case 'cssSelector':
124+
selectors.push(...value);
125+
break;
126+
}
127+
}
128+
}
129+
if(options.ignoreType){
130+
processedOptions.ignoreType = options.ignoreType;
131+
}
132+
}
133+
134+
if (ctx.config.tunnel) {
135+
if (ctx.tunnelDetails && ctx.tunnelDetails.tunnelPort != -1 && ctx.tunnelDetails.tunnelHost != '') {
136+
const tunnelAddress = `http://${ctx.tunnelDetails.tunnelHost}:${ctx.tunnelDetails.tunnelPort}`;
137+
processedOptions.tunnelAddress = tunnelAddress;
138+
ctx.log.debug(`Tunnel address added to processedOptions: ${tunnelAddress}`);
139+
}
140+
}
141+
142+
processedOptions.allowedAssets = ctx.config.allowedAssets;
143+
processedOptions.selectors = selectors;
144+
145+
processedOptions.ignoreDOM = options?.ignoreDOM;
146+
processedOptions.selectDOM = options?.selectDOM;
147+
ctx.log.debug(`Processed options: ${JSON.stringify(processedOptions)}`);
148+
149+
let renderViewports;
150+
if((snapshot.options && snapshot.options.web) || (snapshot.options && snapshot.options.mobile)){
151+
renderViewports = getRenderViewportsForOptions(snapshot.options)
152+
} else {
153+
renderViewports = getRenderViewports(ctx);
154+
}
155+
156+
processedOptions.doRemoteDiscovery = true;
157+
return {
158+
processedSnapshot: {
159+
name: snapshot.name,
160+
url: snapshot.url,
161+
dom: Buffer.from(snapshot.dom.html).toString('base64'),
162+
resources: {},
163+
options: processedOptions,
164+
cookies: Buffer.from(snapshot.dom.cookies).toString('base64'),
165+
renderViewports: renderViewports,
166+
},
167+
warnings: [...optionWarnings, ...snapshot.dom.warnings],
168+
}
169+
}
170+
15171
export default async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
16172
updateLogContext({ task: 'discovery' });
17173
ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);
@@ -24,6 +180,8 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
24180
browsers: {}
25181
};
26182

183+
let processedOptions: Record<string, any> = {};
184+
27185
let globalViewport = ""
28186
let globalBrowser = constants.CHROME
29187
let launchOptions: Record<string, any> = {
@@ -235,7 +393,6 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
235393

236394
let options = snapshot.options;
237395
let optionWarnings: Set<string> = new Set();
238-
let processedOptions: Record<string, any> = {};
239396
let selectors: Array<string> = [];
240397
let ignoreOrSelectDOM: string;
241398
let ignoreOrSelectBoxes: string;
@@ -419,6 +576,7 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
419576
ctx.log.debug(`Network idle failed due to ${error}`);
420577
}
421578

579+
422580
if (ctx.config.allowedAssets && ctx.config.allowedAssets.length) {
423581
for (let assetUrl of ctx.config.allowedAssets) {
424582
if (!cache[assetUrl]) {

src/lib/snapshotQueue.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { access } from "fs";
22
import { Snapshot, Context } from "../types.js";
33
import constants from "./constants.js";
4-
import processSnapshot from "./processSnapshot.js"
4+
import processSnapshot, {prepareSnapshot} from "./processSnapshot.js"
55
import { v4 as uuidv4 } from 'uuid';
66
import { startPolling } from "./utils.js";
77

@@ -312,8 +312,23 @@ export default class Queue {
312312
}
313313
}
314314

315-
// Process and upload snapshot
316-
let { processedSnapshot, warnings, discoveryErrors } = await processSnapshot(snapshot, this.ctx);
315+
let processedSnapshot, warnings, discoveryErrors;
316+
if (this.ctx.env.USE_REMOTE_DISCOVERY) {
317+
this.ctx.log.debug(`Using remote discovery`);
318+
let result = await prepareSnapshot(snapshot, this.ctx);
319+
320+
processedSnapshot = result.processedSnapshot;
321+
warnings = result.warnings;
322+
} else {
323+
this.ctx.log.debug(`Using local discovery`);
324+
let result = await processSnapshot(snapshot, this.ctx);
325+
326+
processedSnapshot = result.processedSnapshot;
327+
warnings = result.warnings;
328+
discoveryErrors = result.discoveryErrors;
329+
}
330+
331+
317332

318333
if (useCapsBuildId) {
319334
if (useKafkaFlowCaps) {

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface Env {
9191
PROJECT_NAME: string | undefined;
9292
SMARTUI_API_PROXY: string | undefined;
9393
SMARTUI_API_SKIP_CERTIFICATES: boolean;
94+
USE_REMOTE_DISCOVERY: boolean;
9495
}
9596

9697
export interface Snapshot {

0 commit comments

Comments
 (0)