Skip to content

Commit e0966ef

Browse files
Merge pull request #425 from percy/PER_4525_ignoreRegionAppiumElements_ios
2 parents a976b52 + 51d856b commit e0966ef

File tree

2 files changed

+198
-6
lines changed

2 files changed

+198
-6
lines changed

percy/providers/genericProvider.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,24 @@ class GenericProvider {
245245
async getRegionsByElements(elementsArray, elements) {
246246
for (let index = 0; index < elements.length; index++) {
247247
try {
248-
const type = await elements[index].getAttribute('class');
249-
const selector = `element: ${index} ${type}`;
248+
let identifier;
249+
const element = elements[index];
250250

251-
const ignoredRegion = await this.getRegionObject(selector, elements[index]);
251+
const capabilities = await this.driver.getCapabilities();
252+
const platformName = capabilities.platformName.toLowerCase();
253+
254+
if (platformName === 'android') {
255+
// Android identifiers
256+
identifier = await element.getAttribute('resource-id') ||
257+
await element.getAttribute('class');
258+
} else if (platformName === 'ios') {
259+
// iOS identifiers
260+
identifier = await element.getAttribute('name') ||
261+
await element.getAttribute('type');
262+
}
263+
264+
const selector = `element: ${index} ${identifier ? `${identifier}` : ''}`.trim();
265+
const ignoredRegion = await this.getRegionObject(selector, element);
252266
elementsArray.push(ignoredRegion);
253267
} catch (e) {
254268
log.info(`Correct Mobile Element not passed at index ${index}.`);

test/percy/providers/genericProvider.test.mjs

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,22 +152,43 @@ describe('GenericProvider', () => {
152152
describe('getRegionsByElements', () => {
153153
let getRegionObjectSpy;
154154
let mockElement;
155+
let mockDriver;
155156

156157
beforeEach(() => {
157158
getRegionObjectSpy = spyOn(provider, 'getRegionObject').and.resolveTo({});
158-
mockElement = {
159-
getAttribute: jasmine.createSpy().and.returnValue('some-class')
159+
mockDriver = {
160+
getCapabilities: jasmine.createSpy('getCapabilities')
160161
};
161162
});
162163

163164
it('should get regions for each element', async () => {
165+
// Set up mock driver with platform capability
166+
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });
167+
164168
const elementsArray = [];
169+
mockElement = {
170+
getAttribute: jasmine.createSpy('getAttribute')
171+
.and.callFake(attr => {
172+
if (attr === 'resource-id') return 'test_id';
173+
if (attr === 'class') return 'android.widget.Button';
174+
return null;
175+
})
176+
};
165177
const elements = [mockElement, mockElement, mockElement];
166178

167-
await provider.getRegionsByElements.call({ driver, getRegionObject: getRegionObjectSpy }, elementsArray, elements);
179+
await provider.getRegionsByElements.call(
180+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
181+
elementsArray,
182+
elements
183+
);
168184

169185
expect(getRegionObjectSpy).toHaveBeenCalledTimes(3);
170186
expect(elementsArray).toEqual([{}, {}, {}]);
187+
188+
// Verify each call had correct selector
189+
for (let i = 0; i < 3; i++) {
190+
expect(getRegionObjectSpy.calls.argsFor(i)[0]).toBe(`element: ${i} test_id`);
191+
}
171192
});
172193

173194
it('should ignore when error', async () => {
@@ -180,6 +201,163 @@ describe('GenericProvider', () => {
180201
// expect(getRegionObjectSpy).not.toHaveBeenCalled();
181202
expect(elementsArray).toEqual([]);
182203
});
204+
205+
describe('Platform specific tests', () => {
206+
it('should handle unknown platform', async () => {
207+
mockDriver.getCapabilities.and.resolveTo({ platformName: 'unknown' });
208+
const elementsArray = [];
209+
mockElement = {
210+
getAttribute: jasmine.createSpy('getAttribute')
211+
.and.returnValue('some-value')
212+
};
213+
214+
await provider.getRegionsByElements.call(
215+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
216+
elementsArray,
217+
[mockElement]
218+
);
219+
220+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
221+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0');
222+
expect(elementsArray).toEqual([{}]);
223+
});
224+
225+
describe('Android', () => {
226+
beforeEach(() => {
227+
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });
228+
});
229+
230+
it('should use resource-id as primary identifier', async () => {
231+
const elementsArray = [];
232+
mockElement = {
233+
getAttribute: jasmine.createSpy('getAttribute')
234+
.and.callFake(attr => {
235+
if (attr === 'resource-id') return 'test_resource_id';
236+
if (attr === 'class') return 'android.widget.Button';
237+
return null;
238+
})
239+
};
240+
241+
await provider.getRegionsByElements.call(
242+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
243+
elementsArray,
244+
[mockElement]
245+
);
246+
247+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
248+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 test_resource_id');
249+
expect(elementsArray).toEqual([{}]);
250+
});
251+
252+
it('should fallback to class when resource-id is not available', async () => {
253+
const elementsArray = [];
254+
mockElement = {
255+
getAttribute: jasmine.createSpy('getAttribute')
256+
.and.callFake(attr => {
257+
if (attr === 'resource-id') return null;
258+
if (attr === 'class') return 'android.widget.Button';
259+
return null;
260+
})
261+
};
262+
263+
await provider.getRegionsByElements.call(
264+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
265+
elementsArray,
266+
[mockElement]
267+
);
268+
269+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
270+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 android.widget.Button');
271+
expect(elementsArray).toEqual([{}]);
272+
});
273+
});
274+
275+
describe('iOS', () => {
276+
beforeEach(() => {
277+
mockDriver.getCapabilities.and.resolveTo({ platformName: 'ios' });
278+
});
279+
280+
it('should use name as primary identifier', async () => {
281+
const elementsArray = [];
282+
mockElement = {
283+
getAttribute: jasmine.createSpy('getAttribute')
284+
.and.callFake(attr => {
285+
if (attr === 'name') return 'TestButton';
286+
if (attr === 'type') return 'XCUIElementTypeButton';
287+
return null;
288+
})
289+
};
290+
291+
await provider.getRegionsByElements.call(
292+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
293+
elementsArray,
294+
[mockElement]
295+
);
296+
297+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
298+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 TestButton');
299+
expect(elementsArray).toEqual([{}]);
300+
});
301+
302+
it('should fallback to type when name is not available', async () => {
303+
const elementsArray = [];
304+
mockElement = {
305+
getAttribute: jasmine.createSpy('getAttribute')
306+
.and.callFake(attr => {
307+
if (attr === 'name') return null;
308+
if (attr === 'type') return 'XCUIElementTypeButton';
309+
return null;
310+
})
311+
};
312+
313+
await provider.getRegionsByElements.call(
314+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
315+
elementsArray,
316+
[mockElement]
317+
);
318+
319+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
320+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 XCUIElementTypeButton');
321+
expect(elementsArray).toEqual([{}]);
322+
});
323+
});
324+
});
325+
326+
describe('Error handling', () => {
327+
it('should handle elements with no identifiers', async () => {
328+
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });
329+
const elementsArray = [];
330+
mockElement = {
331+
getAttribute: jasmine.createSpy('getAttribute').and.returnValue(null)
332+
};
333+
334+
await provider.getRegionsByElements.call(
335+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
336+
elementsArray,
337+
[mockElement]
338+
);
339+
340+
expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
341+
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0');
342+
expect(elementsArray).toEqual([{}]);
343+
});
344+
345+
it('should handle capability errors', async () => {
346+
mockDriver.getCapabilities.and.rejectWith(new Error('Capabilities not found'));
347+
const elementsArray = [];
348+
mockElement = {
349+
getAttribute: jasmine.createSpy('getAttribute')
350+
};
351+
352+
await provider.getRegionsByElements.call(
353+
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
354+
elementsArray,
355+
[mockElement]
356+
);
357+
358+
expect(elementsArray).toEqual([]);
359+
});
360+
});
183361
});
184362

185363
describe('getRegionsByLocation function', () => {

0 commit comments

Comments
 (0)