Skip to content

Commit a1c96aa

Browse files
update find selector logic
1 parent 3153208 commit a1c96aa

File tree

4 files changed

+125
-44
lines changed

4 files changed

+125
-44
lines changed

src/lib/ctx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export default (options: Record<string, string>): Context => {
195195
isSnapshotCaptured: false,
196196
sessionCapabilitiesMap: new Map<string, any[]>(),
197197
buildToSnapshotCountMap: new Map<string, number>(),
198+
sessionIdToSnapshotNameMap: new Map<string, string[]>(),
198199
fetchResultsForBuild: new Array<string>,
199200
orgId: 0,
200201
userId: 0,

src/lib/processSnapshot.ts

Lines changed: 104 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -501,19 +501,19 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
501501
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
502502
switch (key) {
503503
case 'id':
504-
selectors.push(...value.map(e => '#' + e));
504+
selectors.push(...value.map(e => e.startsWith('#') ? e : '#' + e));
505505
break;
506506
case 'class':
507-
selectors.push(...value.map(e => '.' + e));
507+
selectors.push(...value.map(e => e.startsWith('.') ? e : '.' + e));
508508
break;
509509
case 'xpath':
510-
selectors.push(...value.map(e => 'xpath=' + e));
510+
selectors.push(...value.map(e => e.startsWith('xpath=') ? e : 'xpath=' + e));
511511
break;
512512
case 'cssSelector':
513513
selectors.push(...value);
514514
break;
515515
case 'coordinates':
516-
selectors.push(...value.map(e => `coordinates=${e}`));
516+
selectors.push(...value.map(e =>`coordinates=${e}`));
517517
break;
518518
}
519519
}
@@ -707,52 +707,114 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
707707
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, coordinates may not be accurate for multiple viewports`);
708708
}
709709

710-
710+
711711
const coordinateElement = {
712712
type: 'coordinates',
713713
...validation.coords
714714
};
715715
locators.push(coordinateElement as any);
716716
continue;
717-
}
718-
719-
let l = await page.locator(selector).all()
720-
if (l.length === 0) {
721-
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
722-
continue;
723-
}
724-
locators.push(...l);
725-
}
726717

727-
for (const locator of locators) {
728-
if (locator && typeof locator === 'object' && locator.hasOwnProperty('type') && (locator as any).type === 'coordinates') {
729-
const coordLocator = locator as any;
730-
const { top, bottom, left, right } = coordLocator;
731-
processedOptions[ignoreOrSelectBoxes][viewportString].push({
732-
left: left,
733-
top: top,
734-
right: right,
735-
bottom: bottom
736-
});
737-
continue;
738-
}
739-
740-
let bb = await locator.boundingBox();
741-
if (bb) {
742-
// Calculate top and bottom from the bounding box properties
743-
const top = bb.y;
744-
const bottom = bb.y + bb.height;
745-
746-
// Only push if top and bottom are within the calculated height
747-
if (top <= height && bottom <= height) {
748-
processedOptions[ignoreOrSelectBoxes][viewportString].push({
749-
left: bb.x,
750-
top: top,
751-
right: bb.x + bb.width,
752-
bottom: bottom
753-
});
718+
} else {
719+
const isXPath = selector.startsWith('xpath=');
720+
const selectorValue = isXPath ? selector.substring(6) : selector;
721+
722+
const boxes = await page.evaluate(({ selectorValue, isXPath }) => {
723+
try {
724+
// First, determine the page height
725+
const DEFAULT_HEIGHT = 16384;
726+
const DEFAULT_WIDTH = 7680;
727+
const body = document.body;
728+
const html = document.documentElement;
729+
730+
let pageHeight;
731+
let pageWidth;
732+
733+
if (!body || !html) {
734+
pageHeight = DEFAULT_HEIGHT;
735+
pageWidth = DEFAULT_WIDTH;
736+
} else {
737+
const measurements = [
738+
body?.scrollHeight || 0,
739+
body?.offsetHeight || 0,
740+
html?.clientHeight || 0,
741+
html?.scrollHeight || 0,
742+
html?.offsetHeight || 0
743+
];
744+
745+
const allMeasurementsInvalid = measurements.every(measurement => !measurement);
746+
747+
if (allMeasurementsInvalid) {
748+
pageHeight = DEFAULT_HEIGHT;
749+
} else {
750+
pageHeight = Math.max(...measurements);
751+
}
752+
753+
const measurementsWidth = [
754+
body?.scrollWidth || 0,
755+
body?.offsetWidth || 0,
756+
html?.clientWidth || 0,
757+
html?.scrollWidth || 0,
758+
html?.offsetWidth || 0
759+
];
760+
761+
const allMeasurementsInvalidWidth = measurementsWidth.every(measurement => !measurement);
762+
763+
if (allMeasurementsInvalidWidth) {
764+
pageWidth = DEFAULT_WIDTH;
765+
} else {
766+
pageWidth = Math.max(...measurementsWidth);
767+
}
768+
}
769+
770+
let elements = [];
771+
772+
if (isXPath) {
773+
// Use XPath evaluation
774+
const xpathResult = document.evaluate(
775+
selectorValue,
776+
document,
777+
null,
778+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
779+
null
780+
);
781+
782+
for (let i = 0; i < xpathResult.snapshotLength; i++) {
783+
elements.push(xpathResult.snapshotItem(i));
784+
}
785+
} else {
786+
elements = Array.from(document.querySelectorAll(selectorValue));
787+
}
788+
789+
let boxes = [];
790+
791+
elements.forEach((element) => {
792+
const boundingBox = element.getBoundingClientRect();
793+
if (boundingBox && boundingBox.width > 0 && boundingBox.height > 0) {
794+
const box = {
795+
top: boundingBox.top + window.scrollY,
796+
left: boundingBox.left + window.scrollX,
797+
right: boundingBox.left + window.scrollX + boundingBox.width,
798+
bottom: boundingBox.top + window.scrollY + boundingBox.height
799+
};
800+
801+
if (box.bottom <= pageHeight && box.top >= 0) {
802+
boxes.push(box);
803+
}
804+
}
805+
});
806+
807+
return boxes;
808+
809+
} catch (error) {
810+
}
811+
812+
}, { selectorValue, isXPath });
813+
814+
if (boxes && boxes.length > 1) {
815+
processedOptions[ignoreOrSelectBoxes][viewportString].push(...boxes);
754816
} else {
755-
ctx.log.debug(`Bounding box for selector skipped due to exceeding height: ${JSON.stringify({ top, bottom, height })}`);
817+
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
756818
}
757819
}
758820
}

src/lib/snapshotQueue.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,25 @@ export default class Queue {
291291
}
292292

293293
if (!this.ctx.config.delayedUpload && snapshot && snapshot.name && this.snapshotNames.includes(snapshot.name) && !this.ctx.config.allowDuplicateSnapshotNames) {
294-
drop = true;
295-
this.ctx.log.info(`Skipping duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
294+
// check if sessionIdToSnapshotNameMap has snapshot name for the sessionId
295+
if (this.ctx.sessionIdToSnapshotNameMap && snapshot.options && snapshot.options.sessionId) {
296+
if (this.ctx.sessionIdToSnapshotNameMap.has(snapshot.options.sessionId)) {
297+
console.log(`snapshot.options.sessionId`,snapshot.options.sessionId, `this.ctx.sessionIdToSnapshotNameMap`,JSON.stringify([...this.ctx.sessionIdToSnapshotNameMap]));
298+
const existingNames = this.ctx.sessionIdToSnapshotNameMap.get(snapshot.options.sessionId) || [];
299+
if (existingNames.includes(snapshot.name)) {
300+
drop = true;
301+
this.ctx.log.info(`Skipping123 duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
302+
} else {
303+
existingNames.push(snapshot.name);
304+
this.ctx.sessionIdToSnapshotNameMap.set(snapshot.options.sessionId, existingNames);
305+
}
306+
} else {
307+
this.ctx.sessionIdToSnapshotNameMap.set(snapshot.options.sessionId, [snapshot.name]);
308+
}
309+
} else {
310+
drop = true;
311+
this.ctx.log.info(`Skipping duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
312+
}
296313
}
297314

298315
if (this.ctx.config.delayedUpload && snapshot && snapshot.name && this.snapshotNames.includes(snapshot.name)) {

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface Context {
8181
sessionCapabilitiesMap?: Map<string, any[]>;
8282
buildToSnapshotCountMap?: Map<string, number>;
8383
fetchResultsForBuild?: Array<string>;
84+
sessionIdToSnapshotNameMap?: Map<string, string[]>;
8485
orgId?: number;
8586
userId?: number;
8687
mergeBranchSource?: string;

0 commit comments

Comments
 (0)