Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Per-4525] ignore region appium elements ios #425

Merged
merged 7 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions percy/providers/genericProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,24 @@ class GenericProvider {
async getRegionsByElements(elementsArray, elements) {
for (let index = 0; index < elements.length; index++) {
try {
const type = await elements[index].getAttribute('class');
const selector = `element: ${index} ${type}`;
let identifier;
const element = elements[index];

const ignoredRegion = await this.getRegionObject(selector, elements[index]);
const capabilities = await this.driver.getCapabilities();
const platformName = capabilities.platformName.toLowerCase();

if (platformName === 'android') {
// Android identifiers
identifier = await element.getAttribute('resource-id') ||
await element.getAttribute('class');
} else if (platformName === 'ios') {
// iOS identifiers
identifier = await element.getAttribute('name') ||
await element.getAttribute('type');
}

const selector = `element: ${index} ${identifier ? `${identifier}` : ''}`.trim();
const ignoredRegion = await this.getRegionObject(selector, element);
elementsArray.push(ignoredRegion);
} catch (e) {
log.info(`Correct Mobile Element not passed at index ${index}.`);
Expand Down
184 changes: 181 additions & 3 deletions test/percy/providers/genericProvider.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,43 @@ describe('GenericProvider', () => {
describe('getRegionsByElements', () => {
let getRegionObjectSpy;
let mockElement;
let mockDriver;

beforeEach(() => {
getRegionObjectSpy = spyOn(provider, 'getRegionObject').and.resolveTo({});
mockElement = {
getAttribute: jasmine.createSpy().and.returnValue('some-class')
mockDriver = {
getCapabilities: jasmine.createSpy('getCapabilities')
};
});

it('should get regions for each element', async () => {
// Set up mock driver with platform capability
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });

const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.callFake(attr => {
if (attr === 'resource-id') return 'test_id';
if (attr === 'class') return 'android.widget.Button';
return null;
})
};
const elements = [mockElement, mockElement, mockElement];

await provider.getRegionsByElements.call({ driver, getRegionObject: getRegionObjectSpy }, elementsArray, elements);
await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
elements
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(3);
expect(elementsArray).toEqual([{}, {}, {}]);

// Verify each call had correct selector
for (let i = 0; i < 3; i++) {
expect(getRegionObjectSpy.calls.argsFor(i)[0]).toBe(`element: ${i} test_id`);
}
});

it('should ignore when error', async () => {
Expand All @@ -180,6 +201,163 @@ describe('GenericProvider', () => {
// expect(getRegionObjectSpy).not.toHaveBeenCalled();
expect(elementsArray).toEqual([]);
});

describe('Platform specific tests', () => {
it('should handle unknown platform', async () => {
mockDriver.getCapabilities.and.resolveTo({ platformName: 'unknown' });
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.returnValue('some-value')
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0');
expect(elementsArray).toEqual([{}]);
});

describe('Android', () => {
beforeEach(() => {
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });
});

it('should use resource-id as primary identifier', async () => {
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.callFake(attr => {
if (attr === 'resource-id') return 'test_resource_id';
if (attr === 'class') return 'android.widget.Button';
return null;
})
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 test_resource_id');
expect(elementsArray).toEqual([{}]);
});

it('should fallback to class when resource-id is not available', async () => {
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.callFake(attr => {
if (attr === 'resource-id') return null;
if (attr === 'class') return 'android.widget.Button';
return null;
})
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 android.widget.Button');
expect(elementsArray).toEqual([{}]);
});
});

describe('iOS', () => {
beforeEach(() => {
mockDriver.getCapabilities.and.resolveTo({ platformName: 'ios' });
});

it('should use name as primary identifier', async () => {
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.callFake(attr => {
if (attr === 'name') return 'TestButton';
if (attr === 'type') return 'XCUIElementTypeButton';
return null;
})
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 TestButton');
expect(elementsArray).toEqual([{}]);
});

it('should fallback to type when name is not available', async () => {
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
.and.callFake(attr => {
if (attr === 'name') return null;
if (attr === 'type') return 'XCUIElementTypeButton';
return null;
})
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0 XCUIElementTypeButton');
expect(elementsArray).toEqual([{}]);
});
});
});

describe('Error handling', () => {
it('should handle elements with no identifiers', async () => {
mockDriver.getCapabilities.and.resolveTo({ platformName: 'android' });
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute').and.returnValue(null)
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(getRegionObjectSpy).toHaveBeenCalledTimes(1);
expect(getRegionObjectSpy.calls.argsFor(0)[0]).toBe('element: 0');
expect(elementsArray).toEqual([{}]);
});

it('should handle capability errors', async () => {
mockDriver.getCapabilities.and.rejectWith(new Error('Capabilities not found'));
const elementsArray = [];
mockElement = {
getAttribute: jasmine.createSpy('getAttribute')
};

await provider.getRegionsByElements.call(
{ driver: mockDriver, getRegionObject: getRegionObjectSpy },
elementsArray,
[mockElement]
);

expect(elementsArray).toEqual([]);
});
});
});

describe('getRegionsByLocation function', () => {
Expand Down
Loading