Skip to content

Commit 82d18fb

Browse files
authored
Scope selector support for POA (#2077)
* ✨ Add elementSelectorsData support to comparison and update related tests * fix: update comparison schema to allow null values for coordinates and message
1 parent 285d4ad commit 82d18fb

File tree

8 files changed

+636
-9
lines changed

8 files changed

+636
-9
lines changed

packages/client/src/client.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ export class PercyClient {
525525

526526
async createComparison(snapshotId, {
527527
tag, tiles = [], externalDebugUrl, ignoredElementsData,
528-
domInfoSha, consideredElementsData, metadata, sync, regions, algorithm,
528+
domInfoSha, consideredElementsData, elementSelectorsData, metadata, sync, regions, algorithm,
529529
algorithmConfiguration, meta = {}
530530
} = {}) {
531531
validateId('snapshot', snapshotId);
@@ -552,6 +552,7 @@ export class PercyClient {
552552
'ignore-elements-data': ignoredElementsData || null,
553553
regions: regionsArr || null,
554554
'consider-elements-data': consideredElementsData || null,
555+
'element-selectors-data': elementSelectorsData || null,
555556
'dom-info-sha': domInfoSha || null,
556557
sync: !!sync,
557558
metadata: metadata || null

packages/client/test/client.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,17 @@ describe('PercyClient', () => {
14101410
metadata: {
14111411
windowHeight: 1947,
14121412
screenshotType: 'singlepage'
1413+
},
1414+
elementSelectorsData: {
1415+
'#button-id': {
1416+
success: true,
1417+
top: 300,
1418+
left: 100,
1419+
bottom: 350,
1420+
right: 250,
1421+
message: 'Found',
1422+
stacktrace: null
1423+
}
14131424
}
14141425
})).toBeResolved();
14151426

@@ -1429,6 +1440,17 @@ describe('PercyClient', () => {
14291440
'ignore-elements-data': ignoredElementsData,
14301441
'consider-elements-data': consideredElementsData,
14311442
'dom-info-sha': 'abcd=',
1443+
'element-selectors-data': {
1444+
'#button-id': {
1445+
success: true,
1446+
top: 300,
1447+
left: 100,
1448+
bottom: 350,
1449+
right: 250,
1450+
message: 'Found',
1451+
stacktrace: null
1452+
}
1453+
},
14321454
regions: expectedRegions,
14331455
sync: true,
14341456
metadata: {
@@ -1590,6 +1612,7 @@ describe('PercyClient', () => {
15901612
'ignore-elements-data': ignoredElementsData,
15911613
'consider-elements-data': consideredElementsData,
15921614
'dom-info-sha': 'abcd=',
1615+
'element-selectors-data': null,
15931616
regions: expectedRegions,
15941617
sync: true,
15951618
metadata: {
@@ -1665,6 +1688,7 @@ describe('PercyClient', () => {
16651688
'external-debug-url': null,
16661689
'ignore-elements-data': null,
16671690
'consider-elements-data': null,
1691+
'element-selectors-data': null,
16681692
'dom-info-sha': null,
16691693
sync: false,
16701694
regions: null,
@@ -1736,6 +1760,28 @@ describe('PercyClient', () => {
17361760
});
17371761
});
17381762

1763+
it('includes elementSelectorsData when provided', async () => {
1764+
const elementSelectorsData = {
1765+
'#test-id': {
1766+
success: true,
1767+
top: 100,
1768+
left: 100,
1769+
bottom: 200,
1770+
right: 200,
1771+
message: 'Found',
1772+
stacktrace: null
1773+
}
1774+
};
1775+
1776+
await expectAsync(client.createComparison(4567, {
1777+
tag: { name: 'tag' },
1778+
tiles: [{ content: 'test' }],
1779+
elementSelectorsData
1780+
})).toBeResolved();
1781+
1782+
expect(api.requests['/snapshots/4567/comparisons'][0].body.data.attributes['element-selectors-data']).toEqual(elementSelectorsData);
1783+
});
1784+
17391785
it('throws unknown property in invalid comparison json', () => {
17401786
spyOn(fs.promises, 'readFile')
17411787
.withArgs('foo/bar').and.resolveTo('bar');
@@ -2020,6 +2066,7 @@ describe('PercyClient', () => {
20202066
'ignore-elements-data': null,
20212067
'consider-elements-data': null,
20222068
'dom-info-sha': null,
2069+
'element-selectors-data': null,
20232070
sync: false,
20242071
regions: null,
20252072
metadata: null

packages/core/src/config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,21 @@ export const comparisonSchema = {
876876
properties: {
877877
considerElementsData: regionsSchema
878878
}
879+
},
880+
elementSelectorsData: {
881+
type: 'object',
882+
additionalProperties: {
883+
type: 'object',
884+
properties: {
885+
success: { type: 'boolean' },
886+
top: { type: ['number', 'null'] },
887+
left: { type: ['number', 'null'] },
888+
bottom: { type: ['number', 'null'] },
889+
right: { type: ['number', 'null'] },
890+
message: { type: ['string', 'null'] },
891+
stacktrace: { type: ['string', 'null'] }
892+
}
893+
}
879894
}
880895
}
881896
};

packages/core/test/unit/config.test.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,158 @@ describe('SnapshotSchema', () => {
117117
expect(errors[0].message).toBe('must have property scope when property scopeOptions is present');
118118
});
119119
});
120+
121+
describe('ComparisonSchema - elementSelectorsData', () => {
122+
beforeEach(() => {
123+
PercyConfig.addSchema(CoreConfig.schemas);
124+
});
125+
126+
it('should accept valid elementSelectorsData with xpath selector and coordinates', () => {
127+
const comparison = {
128+
name: 'test-snapshot',
129+
tag: {
130+
name: 'test-tag',
131+
width: 1280,
132+
height: 1024
133+
},
134+
elementSelectorsData: {
135+
'//*[@id="__next"]/div/div': {
136+
success: true,
137+
top: 0,
138+
left: 0,
139+
bottom: 1688.0625,
140+
right: 1280,
141+
message: 'Found',
142+
stacktrace: null
143+
}
144+
}
145+
};
146+
147+
const errors = PercyConfig.validate(comparison, '/comparison');
148+
expect(errors).toBe(undefined);
149+
});
150+
151+
it('should accept elementSelectorsData with multiple selectors', () => {
152+
const comparison = {
153+
name: 'test-snapshot',
154+
tag: {
155+
name: 'test-tag',
156+
width: 1280,
157+
height: 1024
158+
},
159+
elementSelectorsData: {
160+
'//*[@id="header"]': {
161+
success: true,
162+
top: 0,
163+
left: 0,
164+
bottom: 100,
165+
right: 1280,
166+
message: 'Found',
167+
stacktrace: null
168+
},
169+
'//*[@id="footer"]': {
170+
success: false,
171+
top: null,
172+
left: null,
173+
bottom: null,
174+
right: null,
175+
message: 'Not found',
176+
stacktrace: 'Element not visible'
177+
}
178+
}
179+
};
180+
181+
const errors = PercyConfig.validate(comparison, '/comparison');
182+
expect(errors).toBe(undefined);
183+
});
184+
185+
it('should accept elementSelectorsData with success false and stacktrace', () => {
186+
const comparison = {
187+
name: 'test-snapshot',
188+
tag: {
189+
name: 'test-tag',
190+
width: 1280,
191+
height: 1024
192+
},
193+
elementSelectorsData: {
194+
'//div[@class="missing"]': {
195+
success: false,
196+
top: 0,
197+
left: 0,
198+
bottom: 0,
199+
right: 0,
200+
message: 'Element not found',
201+
stacktrace: 'Timeout waiting for element'
202+
}
203+
}
204+
};
205+
206+
const errors = PercyConfig.validate(comparison, '/comparison');
207+
expect(errors).toBe(undefined);
208+
});
209+
210+
it('should accept empty elementSelectorsData object', () => {
211+
const comparison = {
212+
name: 'test-snapshot',
213+
tag: {
214+
name: 'test-tag',
215+
width: 1280,
216+
height: 1024
217+
},
218+
elementSelectorsData: {}
219+
};
220+
221+
const errors = PercyConfig.validate(comparison, '/comparison');
222+
expect(errors).toBe(undefined);
223+
});
224+
225+
it('should accept elementSelectorsData with decimal coordinates', () => {
226+
const comparison = {
227+
name: 'test-snapshot',
228+
tag: {
229+
name: 'test-tag',
230+
width: 1280,
231+
height: 1024
232+
},
233+
elementSelectorsData: {
234+
'//*[@id="content"]': {
235+
success: true,
236+
top: 100.5,
237+
left: 50.25,
238+
bottom: 500.75,
239+
right: 1200.125,
240+
message: 'Found',
241+
stacktrace: null
242+
}
243+
}
244+
};
245+
246+
const errors = PercyConfig.validate(comparison, '/comparison');
247+
expect(errors).toBe(undefined);
248+
});
249+
250+
it('should accept elementSelectorsData with negative coordinates', () => {
251+
const comparison = {
252+
name: 'test-snapshot',
253+
tag: {
254+
name: 'test-tag',
255+
width: 1280,
256+
height: 1024
257+
},
258+
elementSelectorsData: {
259+
'//*[@id="offscreen"]': {
260+
success: true,
261+
top: -100,
262+
left: -50,
263+
bottom: 0,
264+
right: 0,
265+
message: 'Found off-screen',
266+
stacktrace: null
267+
}
268+
}
269+
};
270+
271+
const errors = PercyConfig.validate(comparison, '/comparison');
272+
expect(errors).toBe(undefined);
273+
});
274+
});

packages/webdriver-utils/src/providers/automateProvider.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ export default class AutomateProvider extends GenericProvider {
9696
const metadata = {
9797
screenshotType: screenshotType
9898
};
99-
return { tiles: tiles, domInfoSha: tileResponse.dom_sha, metadata: metadata };
99+
return {
100+
tiles: tiles,
101+
domInfoSha: tileResponse.dom_sha,
102+
metadata: metadata,
103+
boundingBoxes: tileResponse.bounding_boxes || null
104+
};
100105
}
101106

102107
async setDebugUrl() {

packages/webdriver-utils/src/providers/genericProvider.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ export default class GenericProvider {
211211
environmentInfo: this.getUserAgentString(this.environmentInfoDetails),
212212
clientInfo: this.getUserAgentString(this.clientInfoDetails),
213213
domInfoSha: tiles.domInfoSha,
214-
metadata: tiles.metadata || null
214+
metadata: tiles.metadata || null,
215+
elementSelectorsData: tiles.boundingBoxes || null
215216
};
216217
}
217218

@@ -255,7 +256,8 @@ export default class GenericProvider {
255256
domInfoSha: await this.getDomContent(),
256257
metadata: {
257258
windowHeight: await this.getWindowHeight()
258-
}
259+
},
260+
boundingBoxes: null
259261
};
260262
}
261263

0 commit comments

Comments
 (0)