From fc03d0cbab90134f1c6d58342afc51b30ac815f9 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Sun, 22 Oct 2023 21:08:19 +0530 Subject: [PATCH 01/13] Fixing FindRegion + Refactor --- packages/webdriver-utils/src/driver.js | 26 +++---- packages/webdriver-utils/src/index.js | 1 + .../src/metadata/desktopMetaData.js | 2 +- .../src/providers/automateProvider.js | 4 - .../src/providers/genericProvider.js | 16 ++-- packages/webdriver-utils/test/driver.test.js | 14 ++++ .../test/metadata/desktopMetaData.test.js | 2 +- .../test/providers/automateProvider.test.js | 6 -- .../test/providers/genericProvider.test.js | 75 ++++++++++++++----- 9 files changed, 96 insertions(+), 50 deletions(-) diff --git a/packages/webdriver-utils/src/driver.js b/packages/webdriver-utils/src/driver.js index 950720173..6d4d68bbd 100644 --- a/packages/webdriver-utils/src/driver.js +++ b/packages/webdriver-utils/src/driver.js @@ -10,6 +10,16 @@ export default class Driver { this.passedCapabilities = passedCapabilities; } + requestPostOptions(command) { + return { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(command) + }; + } + async getCapabilites() { return await Cache.withCache(Cache.caps, this.sessionId, async () => { try { @@ -43,13 +53,7 @@ export default class Driver { if (!command.script.includes('browserstack_executor')) { command.script = `/* percy_automate_script */ \n ${command.script}`; } - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(command) - }; + const options = this.requestPostOptions(command); const baseUrl = `${this.executorUrl}/session/${this.sessionId}/execute/sync`; const response = JSON.parse((await request(baseUrl, options)).body); return response; @@ -68,13 +72,7 @@ export default class Driver { } async findElement(using, value) { - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify({ using, value }) - }; + const options = this.requestPostOptions({ using, value }); const baseUrl = `${this.executorUrl}/session/${this.sessionId}/element`; const response = JSON.parse((await request(baseUrl, options)).body); return response.value; diff --git a/packages/webdriver-utils/src/index.js b/packages/webdriver-utils/src/index.js index 7f20b2415..e977cf4ac 100644 --- a/packages/webdriver-utils/src/index.js +++ b/packages/webdriver-utils/src/index.js @@ -24,6 +24,7 @@ export default class WebdriverUtils { const comparisonData = await automate.screenshot(snapshotName, options); comparisonData.metadata.cliScreenshotStartTime = startTime; comparisonData.metadata.cliScreenshotEndTime = Date.now(); + log.debug(`[${snapshotName}] : Comparison Data: ${JSON.stringify(comparisonData)}`); return comparisonData; } catch (e) { log.error(`[${snapshotName}] : Error - ${e.message}`); diff --git a/packages/webdriver-utils/src/metadata/desktopMetaData.js b/packages/webdriver-utils/src/metadata/desktopMetaData.js index ef0fd1653..67df56131 100644 --- a/packages/webdriver-utils/src/metadata/desktopMetaData.js +++ b/packages/webdriver-utils/src/metadata/desktopMetaData.js @@ -49,7 +49,7 @@ export default class DesktopMetaData { async screenResolution() { return await Cache.withCache(Cache.resolution, this.driver.sessionId, async () => { - const data = await this.driver.executeScript({ script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];', args: [] }); + const data = await this.driver.executeScript({ script: 'return [parseInt(window.screen.width * window.devicePixelRatio).toString(), parseInt(window.screen.height * window.devicePixelRatio).toString()];', args: [] }); const screenInfo = data.value; return `${screenInfo[0]} x ${screenInfo[1]}`; }); diff --git a/packages/webdriver-utils/src/providers/automateProvider.js b/packages/webdriver-utils/src/providers/automateProvider.js index 98f6de64d..5c3b40fd3 100644 --- a/packages/webdriver-utils/src/providers/automateProvider.js +++ b/packages/webdriver-utils/src/providers/automateProvider.js @@ -187,10 +187,6 @@ export default class AutomateProvider extends GenericProvider { const resolution = await this.metaData.screenResolution(); const orientation = (this.metaData.orientation() || automateCaps.deviceOrientation)?.toLowerCase(); - // for android window size only constitutes of browser viewport, hence adding nav / status / url bar heights - [this.header, this.footer] = await this.getHeaderFooter(deviceName, osVersion, browserName); - height = this.metaData.device() && osName?.toLowerCase() === 'android' ? height + this.header + this.footer : height; - return { name: deviceName, osName, diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index bacf51903..f79748740 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -36,6 +36,8 @@ export default class GenericProvider { this.debugUrl = null; this.header = 0; this.footer = 0; + this.pageYShiftFactor = 0; + this.pageXShiftFactor = 0; } addDefaultOptions() { @@ -89,6 +91,10 @@ export default class GenericProvider { const tiles = await this.getTiles(this.header, this.footer, fullscreen); log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); + const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + this.pageYShiftFactor = tag.osName === 'iOS' ? tiles.tiles[0].statusBarHeight : (tiles.tiles[0].statusBarHeight - scrollFactors.value[1]); + this.pageXShiftFactor = tag.osName === 'iOS' ? 0 : (-scrollFactors.value[0]); + const ignoreRegions = await this.findRegions( ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions ); @@ -192,15 +198,15 @@ export default class GenericProvider { } async getRegionObject(selector, elementId) { - const scaleFactor = parseInt(await this.metaData.devicePixelRatio()); + const scaleFactor = await this.metaData.devicePixelRatio(); const rect = await this.driver.rect(elementId); const location = { x: rect.x, y: rect.y }; const size = { height: rect.height, width: rect.width }; const coOrdinates = { - top: Math.floor(location.y * scaleFactor), - bottom: Math.ceil((location.y + size.height) * scaleFactor), - left: Math.floor(location.x * scaleFactor), - right: Math.ceil((location.x + size.width) * scaleFactor) + top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, + bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, + left: Math.floor(location.x * scaleFactor) + this.pageXShiftFactor, + right: Math.ceil((location.x + size.width) * scaleFactor) + this.pageXShiftFactor }; const jsonObject = { diff --git a/packages/webdriver-utils/test/driver.test.js b/packages/webdriver-utils/test/driver.test.js index 6f513e2b6..fa8310c3c 100644 --- a/packages/webdriver-utils/test/driver.test.js +++ b/packages/webdriver-utils/test/driver.test.js @@ -161,4 +161,18 @@ describe('Driver', () => { expect(res).toEqual('mockVal'); }); }); + + describe('requestPostOptions', () => { + const command = { simple: 'test' }; + const expectedResponse = { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(command) + }; + it('returns post options', () => { + expect(driver.requestPostOptions(command)).toEqual(expectedResponse); + }); + }); }); diff --git a/packages/webdriver-utils/test/metadata/desktopMetaData.test.js b/packages/webdriver-utils/test/metadata/desktopMetaData.test.js index 9bec6dc39..4088405c4 100644 --- a/packages/webdriver-utils/test/metadata/desktopMetaData.test.js +++ b/packages/webdriver-utils/test/metadata/desktopMetaData.test.js @@ -108,7 +108,7 @@ describe('DesktopMetaData', () => { screenInfo = await desktopMetaData.screenResolution(); expect(screenInfo).toEqual('1980 x 1080'); expect(executeScriptSpy) - .toHaveBeenCalledWith({ script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];', args: [] }); + .toHaveBeenCalledWith({ script: 'return [parseInt(window.screen.width * window.devicePixelRatio).toString(), parseInt(window.screen.height * window.devicePixelRatio).toString()];', args: [] }); }); }); diff --git a/packages/webdriver-utils/test/providers/automateProvider.test.js b/packages/webdriver-utils/test/providers/automateProvider.test.js index 5dc205913..6f0d0fca7 100644 --- a/packages/webdriver-utils/test/providers/automateProvider.test.js +++ b/packages/webdriver-utils/test/providers/automateProvider.test.js @@ -201,7 +201,6 @@ describe('AutomateProvider', () => { beforeEach(async () => { spyOn(Driver.prototype, 'getCapabilites'); - spyOn(GenericProvider.prototype, 'getHeaderFooter').and.returnValue(Promise.resolve([123, 456])); browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor') .and.returnValue(Promise.resolve({ value: '{ "result": "{\\"dom_sha\\": \\"abc\\", \\"sha\\": [\\"abc-1\\", \\"xyz-2\\"]}", "success":true }' })); executeScriptSpy = spyOn(Driver.prototype, 'executeScript') @@ -244,7 +243,6 @@ describe('AutomateProvider', () => { let windowSizeSpy; let orientationSpy; let resolutionSpy; - let getHeaderFooterSpy; let percyBuildInfo = { id: '123', url: 'https://percy.io/abc/123' @@ -256,7 +254,6 @@ describe('AutomateProvider', () => { percyScreenshotBeginSpy = spyOn(AutomateProvider.prototype, 'percyScreenshotBegin').and.returnValue({ value: '{"buildHash":"12e3","sessionHash":"abc1d","capabilities":{"browserName":"chrome","browserVersion":"113.0","os":"win11","os_version":"11","deviceOrientation":false,"resolution":["1920","1080"]},"success":true,"deviceName":"x.x.x.x"}' }); spyOn(Driver.prototype, 'getCapabilites'); - getHeaderFooterSpy = spyOn(GenericProvider.prototype, 'getHeaderFooter').and.returnValue(Promise.resolve([0, 0])); windowSizeSpy = spyOn(DesktopMetaData.prototype, 'windowSize') .and.returnValue(Promise.resolve({ width: 1000, height: 1000 })); resolutionSpy = spyOn(DesktopMetaData.prototype, 'screenResolution') @@ -275,7 +272,6 @@ describe('AutomateProvider', () => { expect(windowSizeSpy).toHaveBeenCalledTimes(1); expect(resolutionSpy).toHaveBeenCalledTimes(1); expect(orientationSpy).toHaveBeenCalledTimes(1); - expect(getHeaderFooterSpy).toHaveBeenCalledTimes(1); expect(res).toEqual({ name: 'Windows_11_chrome_113', osName: 'Windows', @@ -296,7 +292,6 @@ describe('AutomateProvider', () => { percyScreenshotBeginSpy = spyOn(AutomateProvider.prototype, 'percyScreenshotBegin').and.returnValue({ value: '{"buildHash":"12e3","sessionHash":"abc1d","capabilities":{"browserName":"chrome_android","browserVersion":"chrome_android","os":"android","os_version":"11","deviceOrientation":"portrait","resolution":["1920","1080"]},"success":true,"deviceName":"Samsung Galaxy S21"}' }); spyOn(Driver.prototype, 'getCapabilites'); - getHeaderFooterSpy = spyOn(GenericProvider.prototype, 'getHeaderFooter').and.returnValue(Promise.resolve([0, 0])); windowSizeSpy = spyOn(MobileMetaData.prototype, 'windowSize') .and.returnValue(Promise.resolve({ width: 1000, height: 1000 })); resolutionSpy = spyOn(MobileMetaData.prototype, 'screenResolution') @@ -315,7 +310,6 @@ describe('AutomateProvider', () => { expect(windowSizeSpy).toHaveBeenCalledTimes(1); expect(resolutionSpy).toHaveBeenCalledTimes(1); expect(orientationSpy).toHaveBeenCalledTimes(1); - expect(getHeaderFooterSpy).toHaveBeenCalledTimes(1); expect(res).toEqual({ name: 'Samsung Galaxy S21', osName: 'Android', diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index ccecda797..598e25630 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -140,10 +140,13 @@ describe('GenericProvider', () => { let getTilesSpy; beforeEach(() => { + const scrollFactors = { value: [0, 10] }; getTagSpy = spyOn(GenericProvider.prototype, 'getTag').and.returnValue(Promise.resolve('mock-tag')); getTilesSpy = spyOn(GenericProvider.prototype, 'getTiles').and.returnValue(Promise.resolve({ tiles: 'mock-tile', domInfoSha: 'mock-dom-sha' })); spyOn(DesktopMetaData.prototype, 'windowSize') .and.returnValue(Promise.resolve({ width: 1920, height: 1080 })); + spyOn(Driver.prototype, 'executeScript') + .and.returnValue(scrollFactors); }); it('calls correct funcs', async () => { @@ -184,28 +187,62 @@ describe('GenericProvider', () => { describe('getRegionObject', () => { let provider; let mockLocation = { x: 10, y: 20, width: 100, height: 200 }; - beforeEach(() => { - // mock metadata - provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); - spyOn(DesktopMetaData.prototype, 'devicePixelRatio') - .and.returnValue(1); - spyOn(Driver.prototype, 'rect').and.returnValue(Promise.resolve(mockLocation)); + + describe('When on Tile 0', () => { + beforeEach(() => { + // mock metadata + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + spyOn(DesktopMetaData.prototype, 'devicePixelRatio') + .and.returnValue(1); + spyOn(Driver.prototype, 'rect').and.returnValue(Promise.resolve(mockLocation)); + }); + + it('should return a JSON object with the correct selector and coordinates for tile 0', async () => { + await provider.createDriver(); + + // Call function with mock data + const selector = 'mock-selector'; + const result = await provider.getRegionObject(selector, 'mockElementId'); + + // Assert expected result + expect(result.selector).toEqual(selector); + expect(result.coOrdinates).toEqual({ + top: mockLocation.y, + bottom: mockLocation.y + mockLocation.height, + left: mockLocation.x, + right: mockLocation.x + mockLocation.width + }); + }); }); - it('should return a JSON object with the correct selector and coordinates', async () => { - await provider.createDriver(); + describe('When on Tile 1', () => { + beforeEach(() => { + // mock metadata + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + spyOn(DesktopMetaData.prototype, 'devicePixelRatio') + .and.returnValue(1); + spyOn(Driver.prototype, 'rect').and.returnValue(Promise.resolve(mockLocation)); + provider.pageYShiftFactor = -10; + }); - // Call function with mock data - const selector = 'mock-selector'; - const result = await provider.getRegionObject(selector, 'mockElementId'); - - // Assert expected result - expect(result.selector).toEqual(selector); - expect(result.coOrdinates).toEqual({ - top: mockLocation.y, - bottom: mockLocation.y + mockLocation.height, - left: mockLocation.x, - right: mockLocation.x + mockLocation.width + afterEach(() => { + provider.pageYShiftFactor = 0; + }); + it('should return a JSON object with the correct selector and coordinates', async () => { + await provider.createDriver(); + + // Call function with mock data + const selector = 'mock-selector'; + const result = await provider.getRegionObject(selector, 'mockElementId'); + + // Assert expected result + expect(result.selector).toEqual(selector); + expect(result.coOrdinates).toEqual({ + top: mockLocation.y + provider.pageYShiftFactor, + bottom: mockLocation.y + mockLocation.height + provider.pageYShiftFactor, + left: mockLocation.x, + right: mockLocation.x + mockLocation.width + }); }); }); }); From 854ee79c7618290688022e907626797db43b609e Mon Sep 17 00:00:00 2001 From: amit3200 Date: Sun, 22 Oct 2023 23:05:02 +0530 Subject: [PATCH 02/13] Adding More Tests --- .../test/providers/genericProvider.test.js | 139 ++++++++++++++---- 1 file changed, 114 insertions(+), 25 deletions(-) diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 598e25630..554b65652 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -138,34 +138,123 @@ describe('GenericProvider', () => { describe('screenshot', () => { let getTagSpy; let getTilesSpy; + let iOSGetTagSpy; + let iOSGetTilesSpy; - beforeEach(() => { - const scrollFactors = { value: [0, 10] }; - getTagSpy = spyOn(GenericProvider.prototype, 'getTag').and.returnValue(Promise.resolve('mock-tag')); - getTilesSpy = spyOn(GenericProvider.prototype, 'getTiles').and.returnValue(Promise.resolve({ tiles: 'mock-tile', domInfoSha: 'mock-dom-sha' })); - spyOn(DesktopMetaData.prototype, 'windowSize') - .and.returnValue(Promise.resolve({ width: 1920, height: 1080 })); - spyOn(Driver.prototype, 'executeScript') - .and.returnValue(scrollFactors); + describe('With Desktop', () => { + let desktopTag; + let desktopTiles; + beforeEach(() => { + desktopTag = { + name: 'Windows_11_Chrome_103', + osName: 'Windows', + osVersion: '11', + width: 1000, + height: 1000, + orientation: 'landscape', + browserName: 'chrome', + browserVersion: '103', + resolution: '1980 x 1080' + }; + desktopTiles = { + tiles: [{ + statusBarHeight: 0, + sha: 'abc', + navBarHeight: 0, + headerHeight: 0, + footerHeight: 0, + fullscreen: false + }], + domInfoSha: 'mock-dom-sha' + }; + const scrollFactors = { value: [0, 10] }; + getTagSpy = spyOn(GenericProvider.prototype, 'getTag').and.returnValue(Promise.resolve(desktopTag)); + getTilesSpy = spyOn(GenericProvider.prototype, 'getTiles').and.returnValue(Promise.resolve(desktopTiles)); + spyOn(DesktopMetaData.prototype, 'windowSize') + .and.returnValue(Promise.resolve({ width: 1920, height: 1080 })); + spyOn(Driver.prototype, 'executeScript') + .and.returnValue(scrollFactors); + }); + + it('calls correct funcs', async () => { + genericProvider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'local-poc-poa', 'staging-poc-poa', {}); + await genericProvider.createDriver(); + let res = await genericProvider.screenshot('mock-name', {}); + expect(getTagSpy).toHaveBeenCalledTimes(1); + expect(genericProvider.pageYShiftFactor).toEqual(-10); + expect(genericProvider.pageXShiftFactor).toEqual(-0); + expect(getTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); + expect(res).toEqual({ + name: 'mock-name', + tag: desktopTag, + tiles: desktopTiles.tiles, + externalDebugUrl: 'https://localhost/v1', + environmentInfo: 'staging-poc-poa', + ignoredElementsData: { ignoreElementsData: [] }, + consideredElementsData: { considerElementsData: [] }, + clientInfo: 'local-poc-poa', + domInfoSha: 'mock-dom-sha', + metadata: null + }); + }); }); - it('calls correct funcs', async () => { - genericProvider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'local-poc-poa', 'staging-poc-poa', {}); - await genericProvider.createDriver(); - let res = await genericProvider.screenshot('mock-name', {}); - expect(getTagSpy).toHaveBeenCalledTimes(1); - expect(getTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); - expect(res).toEqual({ - name: 'mock-name', - tag: 'mock-tag', - tiles: 'mock-tile', - externalDebugUrl: 'https://localhost/v1', - environmentInfo: 'staging-poc-poa', - ignoredElementsData: { ignoreElementsData: [] }, - consideredElementsData: { considerElementsData: [] }, - clientInfo: 'local-poc-poa', - domInfoSha: 'mock-dom-sha', - metadata: null + describe('With Mobile iOS', () => { + let iosTag; + let iosTiles; + + beforeEach(() => { + const scrollFactors = { value: [0, 10] }; + iosTag = { + name: 'iPhone 11 Pro', + osName: 'iOS', + osVersion: '15', + width: 1000, + height: 1000, + orientation: 'potrait', + browserName: 'safari', + browserVersion: '15', + resolution: '1980 x 1080' + }; + iosTiles = { + tiles: [{ + statusBarHeight: 132, + sha: 'abc', + navBarHeight: 0, + headerHeight: 0, + footerHeight: 0, + fullscreen: false + }], + domInfoSha: 'mock-dom-sha' + }; + iOSGetTagSpy = spyOn(GenericProvider.prototype, 'getTag').and.returnValue(Promise.resolve(iosTag)); + iOSGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles').and.returnValue(Promise.resolve(iosTiles)); + spyOn(DesktopMetaData.prototype, 'windowSize') + .and.returnValue(Promise.resolve({ width: 1920, height: 1080 })); + spyOn(Driver.prototype, 'executeScript') + .and.returnValue(scrollFactors); + }); + + it('calls correct funcs with iOS', async () => { + genericProvider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'local-poc-poa', 'staging-poc-poa', {}); + await genericProvider.createDriver(); + let res = await genericProvider.screenshot('mock-name', {}); + expect(iOSGetTagSpy).toHaveBeenCalledTimes(1); + expect(genericProvider.pageYShiftFactor).toEqual(132); + expect(genericProvider.pageXShiftFactor).toEqual(0); + expect(iOSGetTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); + expect(res).toEqual({ + name: 'mock-name', + tag: iosTag, + tiles: iosTiles.tiles, + externalDebugUrl: 'https://localhost/v1', + environmentInfo: 'staging-poc-poa', + ignoredElementsData: { ignoreElementsData: [] }, + consideredElementsData: { considerElementsData: [] }, + clientInfo: 'local-poc-poa', + domInfoSha: 'mock-dom-sha', + metadata: null + }); }); }); }); From bd070e832ea5c3af47e44dc62758e84cd36b2e22 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 25 Oct 2023 12:44:55 +0530 Subject: [PATCH 03/13] Making logic more functional for IOS --- .../src/providers/genericProvider.js | 17 +++++++++ .../test/providers/genericProvider.test.js | 38 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index f79748740..6059eae4a 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -38,6 +38,9 @@ export default class GenericProvider { this.footer = 0; this.pageYShiftFactor = 0; this.pageXShiftFactor = 0; + this.scrollXFactor = 0; + this.scrollYFactor = 0; + this.currentOs = null; } addDefaultOptions() { @@ -92,6 +95,9 @@ export default class GenericProvider { log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + this.scrollXFactor = scrollFactors.value[0]; + this.scrollYFactor = scrollFactors.value[1]; + this.currentOs = tag.osName; this.pageYShiftFactor = tag.osName === 'iOS' ? tiles.tiles[0].statusBarHeight : (tiles.tiles[0].statusBarHeight - scrollFactors.value[1]); this.pageXShiftFactor = tag.osName === 'iOS' ? 0 : (-scrollFactors.value[0]); @@ -197,11 +203,22 @@ export default class GenericProvider { ]; } + updateYFactor(location) { + if (this.currentOs === 'iOS') { + if (location.y === 0) { + this.pageYShiftFactor += (-this.scrollYFactor); + } + } + } + async getRegionObject(selector, elementId) { const scaleFactor = await this.metaData.devicePixelRatio(); const rect = await this.driver.rect(elementId); const location = { x: rect.x, y: rect.y }; const size = { height: rect.height, width: rect.width }; + // Update YFactor Element is not visible in viewport + // In case of iOS if the element is not visible in viewport it gives 0,0 as coordinate. + this.updateYFactor(location); const coOrdinates = { top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 554b65652..c757aba95 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -273,6 +273,44 @@ describe('GenericProvider', () => { }); }); + describe('updateYFactor', () => { + let provider; + + describe('When iOS', () => { + beforeEach(() => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentOs = 'iOS'; + provider.scrollYFactor = 10; + provider.pageYShiftFactor = 0; + }); + + it('should update pageYShiftFactor for iOS when location.y is 0', () => { + provider.updateYFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(-provider.scrollYFactor); + }); + + it('should not update pageYShiftFactor for iOS when location.y is not 0', () => { + // Location.y is not 0 + provider.updateYFactor({ y: 5 }); + expect(provider.pageYShiftFactor).toBe(0); + }); + }); + + describe('When Other', () => { + beforeEach(() => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentOs = 'Android'; + provider.scrollYFactor = 10; + provider.pageYShiftFactor = 0; + }); + + it('should not update pageYShiftFactor for non-iOS platforms', () => { + provider.updateYFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(0); + }); + }); + }); + describe('getRegionObject', () => { let provider; let mockLocation = { x: 10, y: 20, width: 100, height: 200 }; From 6c9f69352e168578b797134ecd45b0f80d17b695 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 25 Oct 2023 16:51:20 +0530 Subject: [PATCH 04/13] Refactoring code as per comments --- packages/webdriver-utils/src/driver.js | 6 +- .../src/providers/genericProvider.js | 26 ++++----- packages/webdriver-utils/test/driver.test.js | 2 +- .../test/providers/genericProvider.test.js | 56 +++++++++++-------- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/packages/webdriver-utils/src/driver.js b/packages/webdriver-utils/src/driver.js index 6d4d68bbd..87760778e 100644 --- a/packages/webdriver-utils/src/driver.js +++ b/packages/webdriver-utils/src/driver.js @@ -10,7 +10,7 @@ export default class Driver { this.passedCapabilities = passedCapabilities; } - requestPostOptions(command) { + static requestPostOptions(command) { return { method: 'POST', headers: { @@ -53,7 +53,7 @@ export default class Driver { if (!command.script.includes('browserstack_executor')) { command.script = `/* percy_automate_script */ \n ${command.script}`; } - const options = this.requestPostOptions(command); + const options = Driver.requestPostOptions(command); const baseUrl = `${this.executorUrl}/session/${this.sessionId}/execute/sync`; const response = JSON.parse((await request(baseUrl, options)).body); return response; @@ -72,7 +72,7 @@ export default class Driver { } async findElement(using, value) { - const options = this.requestPostOptions({ using, value }); + const options = Driver.requestPostOptions({ using, value }); const baseUrl = `${this.executorUrl}/session/${this.sessionId}/element`; const response = JSON.parse((await request(baseUrl, options)).body); return response.value; diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index 6059eae4a..bb5371a0d 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -36,11 +36,10 @@ export default class GenericProvider { this.debugUrl = null; this.header = 0; this.footer = 0; - this.pageYShiftFactor = 0; + this.statusBarHeight = 0; this.pageXShiftFactor = 0; - this.scrollXFactor = 0; - this.scrollYFactor = 0; - this.currentOs = null; + this.pageYShiftFactor = 0; + this.currentOperatingSystem = null; } addDefaultOptions() { @@ -94,12 +93,8 @@ export default class GenericProvider { const tiles = await this.getTiles(this.header, this.footer, fullscreen); log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); - const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); - this.scrollXFactor = scrollFactors.value[0]; - this.scrollYFactor = scrollFactors.value[1]; - this.currentOs = tag.osName; - this.pageYShiftFactor = tag.osName === 'iOS' ? tiles.tiles[0].statusBarHeight : (tiles.tiles[0].statusBarHeight - scrollFactors.value[1]); - this.pageXShiftFactor = tag.osName === 'iOS' ? 0 : (-scrollFactors.value[0]); + this.currentOperatingSystem = tag.osName; + this.statusBarHeight = tiles.tiles[0].statusBarHeight; const ignoreRegions = await this.findRegions( ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions @@ -203,10 +198,13 @@ export default class GenericProvider { ]; } - updateYFactor(location) { - if (this.currentOs === 'iOS') { + async updatePageShiftFactor(location) { + const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); + this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); + if (this.currentOperatingSystem === 'iOS') { if (location.y === 0) { - this.pageYShiftFactor += (-this.scrollYFactor); + this.pageYShiftFactor += (-scrollFactors.value[1]); } } } @@ -218,7 +216,7 @@ export default class GenericProvider { const size = { height: rect.height, width: rect.width }; // Update YFactor Element is not visible in viewport // In case of iOS if the element is not visible in viewport it gives 0,0 as coordinate. - this.updateYFactor(location); + await this.updatePageShiftFactor(location); const coOrdinates = { top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, diff --git a/packages/webdriver-utils/test/driver.test.js b/packages/webdriver-utils/test/driver.test.js index fa8310c3c..74dc58b79 100644 --- a/packages/webdriver-utils/test/driver.test.js +++ b/packages/webdriver-utils/test/driver.test.js @@ -172,7 +172,7 @@ describe('Driver', () => { body: JSON.stringify(command) }; it('returns post options', () => { - expect(driver.requestPostOptions(command)).toEqual(expectedResponse); + expect(Driver.requestPostOptions(command)).toEqual(expectedResponse); }); }); }); diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index c757aba95..f3ff1af60 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -167,13 +167,10 @@ describe('GenericProvider', () => { }], domInfoSha: 'mock-dom-sha' }; - const scrollFactors = { value: [0, 10] }; getTagSpy = spyOn(GenericProvider.prototype, 'getTag').and.returnValue(Promise.resolve(desktopTag)); getTilesSpy = spyOn(GenericProvider.prototype, 'getTiles').and.returnValue(Promise.resolve(desktopTiles)); spyOn(DesktopMetaData.prototype, 'windowSize') .and.returnValue(Promise.resolve({ width: 1920, height: 1080 })); - spyOn(Driver.prototype, 'executeScript') - .and.returnValue(scrollFactors); }); it('calls correct funcs', async () => { @@ -181,8 +178,7 @@ describe('GenericProvider', () => { await genericProvider.createDriver(); let res = await genericProvider.screenshot('mock-name', {}); expect(getTagSpy).toHaveBeenCalledTimes(1); - expect(genericProvider.pageYShiftFactor).toEqual(-10); - expect(genericProvider.pageXShiftFactor).toEqual(-0); + expect(genericProvider.statusBarHeight).toEqual(0); expect(getTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); expect(res).toEqual({ name: 'mock-name', @@ -240,8 +236,7 @@ describe('GenericProvider', () => { await genericProvider.createDriver(); let res = await genericProvider.screenshot('mock-name', {}); expect(iOSGetTagSpy).toHaveBeenCalledTimes(1); - expect(genericProvider.pageYShiftFactor).toEqual(132); - expect(genericProvider.pageXShiftFactor).toEqual(0); + expect(genericProvider.statusBarHeight).toEqual(132); expect(iOSGetTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); expect(res).toEqual({ name: 'mock-name', @@ -273,41 +268,50 @@ describe('GenericProvider', () => { }); }); - describe('updateYFactor', () => { + describe('updatePageShiftFactor', () => { let provider; describe('When iOS', () => { - beforeEach(() => { + beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); - provider.currentOs = 'iOS'; - provider.scrollYFactor = 10; + await provider.createDriver(); + spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); + provider.currentOperatingSystem = 'iOS'; provider.pageYShiftFactor = 0; + provider.statusBarHeight = 0; }); - it('should update pageYShiftFactor for iOS when location.y is 0', () => { - provider.updateYFactor({ y: 0 }); - expect(provider.pageYShiftFactor).toBe(-provider.scrollYFactor); + it('should update pageYShiftFactor for iOS when location.y is 0', async () => { + await provider.updatePageShiftFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(-10); }); - it('should not update pageYShiftFactor for iOS when location.y is not 0', () => { + it('should not update pageYShiftFactor for iOS when location.y is not 0', async () => { // Location.y is not 0 - provider.updateYFactor({ y: 5 }); + await provider.updatePageShiftFactor({ y: 5 }); expect(provider.pageYShiftFactor).toBe(0); }); }); describe('When Other', () => { - beforeEach(() => { + beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); - provider.currentOs = 'Android'; - provider.scrollYFactor = 10; + await provider.createDriver(); + provider.currentOperatingSystem = 'Android'; provider.pageYShiftFactor = 0; }); - it('should not update pageYShiftFactor for non-iOS platforms', () => { - provider.updateYFactor({ y: 0 }); + it('should not update pageYShiftFactor for non-iOS platforms', async () => { + spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 0] }); + await provider.updatePageShiftFactor({ y: 0 }); expect(provider.pageYShiftFactor).toBe(0); }); + + it('should update pageYShiftFactor for non-iOS platforms accordingly if scrolled', async () => { + spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); + await provider.updatePageShiftFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(-10); + }); }); }); @@ -316,11 +320,14 @@ describe('GenericProvider', () => { let mockLocation = { x: 10, y: 20, width: 100, height: 200 }; describe('When on Tile 0', () => { - beforeEach(() => { + beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); + spyOn(Driver.prototype, 'executeScript') + .and.returnValue({ value: [0, 0] }); spyOn(Driver.prototype, 'rect').and.returnValue(Promise.resolve(mockLocation)); }); @@ -343,11 +350,14 @@ describe('GenericProvider', () => { }); describe('When on Tile 1', () => { - beforeEach(() => { + beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); + spyOn(Driver.prototype, 'executeScript') + .and.returnValue({ value: [0, 0] }); spyOn(Driver.prototype, 'rect').and.returnValue(Promise.resolve(mockLocation)); provider.pageYShiftFactor = -10; }); From 91011fb4490663a5e1c2a1964e22fa51bc3ecd22 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 25 Oct 2023 18:13:07 +0530 Subject: [PATCH 05/13] Updating Comment for Info --- packages/webdriver-utils/src/providers/genericProvider.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index bb5371a0d..d6c3d694e 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -214,8 +214,10 @@ export default class GenericProvider { const rect = await this.driver.rect(elementId); const location = { x: rect.x, y: rect.y }; const size = { height: rect.height, width: rect.width }; - // Update YFactor Element is not visible in viewport - // In case of iOS if the element is not visible in viewport it gives 0,0 as coordinate. + // Update pageShiftFactor Element is not visible in viewport + // In case of iOS if the element is not visible in viewport it gives 0 for x-y coordinate. + // In case of iOS if the element is partially visible it gives negative x-y coordinate. + // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not. await this.updatePageShiftFactor(location); const coOrdinates = { top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, From 0eb37be650b0135633dd7c5a906ca2e42d05d305 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Tue, 31 Oct 2023 19:22:34 +0530 Subject: [PATCH 06/13] Fixing ignore region --- packages/webdriver-utils/src/driver.js | 21 ++++++++ .../src/providers/genericProvider.js | 48 +++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/packages/webdriver-utils/src/driver.js b/packages/webdriver-utils/src/driver.js index 87760778e..bcadfc4a7 100644 --- a/packages/webdriver-utils/src/driver.js +++ b/packages/webdriver-utils/src/driver.js @@ -77,4 +77,25 @@ export default class Driver { const response = JSON.parse((await request(baseUrl, options)).body); return response.value; } + + async findElementBoundingBox(using, value) { + if (using === 'xpath') { + return await this.findElementXpath(value); + } else if (using === 'css selector') { + return await this.findElementSelector(value); + } + } + + async findElementXpath(xpath) { + const command = { script: `return document.evaluate('${xpath}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect();`, args: [] }; + const response = await this.executeScript(command); + return response.value; + } + + async findElementSelector(selector) { + selector = selector.replace('\\', '\\\\'); + const command = { script: `return document.querySelector('${selector}').getBoundingClientRect();`, args: [] }; + const response = await this.executeScript(command); + return response.value; + } } diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index d6c3d694e..2adbad0e8 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -40,6 +40,8 @@ export default class GenericProvider { this.pageXShiftFactor = 0; this.pageYShiftFactor = 0; this.currentOperatingSystem = null; + this.removeElementShiftFactor = 50000; + this.initialScrollFactor = { value: [0, 0] }; } addDefaultOptions() { @@ -203,7 +205,11 @@ export default class GenericProvider { this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); if (this.currentOperatingSystem === 'iOS') { - if (location.y === 0) { + console.log(scrollFactors, this.initialScrollFactor); + if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { + this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); + this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); + } else if (location.y === 0) { this.pageYShiftFactor += (-scrollFactors.value[1]); } } @@ -234,13 +240,34 @@ export default class GenericProvider { return jsonObject; } + async getRegionObjectFromBoundingBox(selector, element) { + const scaleFactor = await this.metaData.devicePixelRatio(); + let headerAdjustment = 0; + if (this.currentOperatingSystem === 'iOS') { + headerAdjustment = this.statusBarHeight; + } + const coOrdinates = { + top: Math.floor(element.y * scaleFactor) + headerAdjustment, + bottom: Math.ceil((element.y + element.height) * scaleFactor) + headerAdjustment, + left: Math.floor(element.x * scaleFactor), + right: Math.ceil((element.x + element.width) * scaleFactor) + }; + + const jsonObject = { + selector, + coOrdinates + }; + + return jsonObject; + } + async getSeleniumRegionsBy(findBy, elements) { const regionsArray = []; for (const idx in elements) { try { - const element = await this.driver.findElement(findBy, elements[idx]); + const boundingBoxRegion = await this.driver.findElementBoundingBox(findBy, elements[idx]); const selector = `${findBy}: ${elements[idx]}`; - const region = await this.getRegionObject(selector, element[Object.keys(element)[0]]); + const region = await this.getRegionObjectFromBoundingBox(selector, boundingBoxRegion); regionsArray.push(region); } catch (e) { log.warn(`Selenium Element with ${findBy}: ${elements[idx]} not found. Ignoring this ${findBy}.`); @@ -250,12 +277,24 @@ export default class GenericProvider { return regionsArray; } + async getInitialPosition() { + if (this.currentOperatingSystem === 'iOS') { + this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + } + } + + async scrollToInitialPosition(x, y) { + if (this.currentOperatingSystem === 'iOS') { + await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); + } + } + async getSeleniumRegionsByElement(elements) { const regionsArray = []; + await this.getInitialPosition(); for (let index = 0; index < elements.length; index++) { try { const selector = `element: ${index}`; - const region = await this.getRegionObject(selector, elements[index]); regionsArray.push(region); } catch (e) { @@ -263,6 +302,7 @@ export default class GenericProvider { log.debug(e.toString()); } } + await this.scrollToInitialPosition(this.initialScrollFactor.value[0], this.initialScrollFactor.value[1]); return regionsArray; } From 2d023127cbbe5568d4dc598567242abfe1a77213 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 1 Nov 2023 06:09:37 +0530 Subject: [PATCH 07/13] Fixing few stuff --- .../src/providers/genericProvider.js | 1 - packages/webdriver-utils/test/driver.test.js | 60 ++++++ .../test/providers/genericProvider.test.js | 183 ++++++++++++++++-- 3 files changed, 224 insertions(+), 20 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index 2adbad0e8..2b972b7c1 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -205,7 +205,6 @@ export default class GenericProvider { this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); if (this.currentOperatingSystem === 'iOS') { - console.log(scrollFactors, this.initialScrollFactor); if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); diff --git a/packages/webdriver-utils/test/driver.test.js b/packages/webdriver-utils/test/driver.test.js index 74dc58b79..3b90da83c 100644 --- a/packages/webdriver-utils/test/driver.test.js +++ b/packages/webdriver-utils/test/driver.test.js @@ -151,6 +151,66 @@ describe('Driver', () => { }); }); + describe('findElementBoundingBox', () => { + let xpathFindElementSpy; + let cssSelectorFindElementSpy; + beforeEach(() => { + xpathFindElementSpy = spyOn(Driver.prototype, 'findElementXpath').and.returnValue(Promise.resolve({ x: 0, y: 10, height: 100, width: 100 })); + cssSelectorFindElementSpy = spyOn(Driver.prototype, 'findElementSelector').and.returnValue(Promise.resolve({ x: 0, y: 10, height: 100, width: 100 })); + }); + describe('when xpath is passed', () => { + it('calls the required function', async () => { + const res = await driver.findElementBoundingBox('xpath', '/xpath1'); + expect(cssSelectorFindElementSpy).toHaveBeenCalledTimes(0); + expect(xpathFindElementSpy).toHaveBeenCalledTimes(1); + expect(xpathFindElementSpy).toHaveBeenCalledWith('/xpath1'); + expect(res).toEqual({ x: 0, y: 10, height: 100, width: 100 }); + }); + }); + + describe('when selector is passed', () => { + it('calls the required function', async () => { + const res = await driver.findElementBoundingBox('css selector', '#id1'); + expect(xpathFindElementSpy).toHaveBeenCalledTimes(0); + expect(cssSelectorFindElementSpy).toHaveBeenCalledTimes(1); + expect(cssSelectorFindElementSpy).toHaveBeenCalledWith('#id1'); + expect(res).toEqual({ x: 0, y: 10, height: 100, width: 100 }); + }); + }); + + describe('when invalid is passed', () => { + it('calls nothing', async () => { + await driver.findElementBoundingBox('abc', '#id1'); + expect(xpathFindElementSpy).toHaveBeenCalledTimes(0); + expect(cssSelectorFindElementSpy).toHaveBeenCalledTimes(0); + }); + }); + }); + + describe('findElementXpath', () => { + let executeScriptSpy; + beforeEach(() => { + executeScriptSpy = spyOn(Driver.prototype, 'executeScript').and.returnValue(Promise.resolve({ value: { x: 0, y: 10, height: 100, width: 100 } })); + }); + it('calls requests', async () => { + const res = await driver.findElementXpath('/xpath1'); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: "return document.evaluate('/xpath1', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect();", args: [] }); + expect(res).toEqual({ x: 0, y: 10, height: 100, width: 100 }); + }); + }); + + describe('findElementSelector', () => { + let executeScriptSpy; + beforeEach(() => { + executeScriptSpy = spyOn(Driver.prototype, 'executeScript').and.returnValue(Promise.resolve({ value: { x: 0, y: 10, height: 100, width: 100 } })); + }); + it('calls requests', async () => { + const res = await driver.findElementSelector('#id1'); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: "return document.querySelector('#id1').getBoundingClientRect();", args: [] }); + expect(res).toEqual({ x: 0, y: 10, height: 100, width: 100 }); + }); + }); + describe('rect', () => { it('calls requests', async () => { const elementId = 'element'; diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index f3ff1af60..e2cdb8379 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -281,15 +281,36 @@ describe('GenericProvider', () => { provider.statusBarHeight = 0; }); - it('should update pageYShiftFactor for iOS when location.y is 0', async () => { - await provider.updatePageShiftFactor({ y: 0 }); - expect(provider.pageYShiftFactor).toBe(-10); + describe('when element is visible in viewport', () => { + beforeEach(() => { + provider.initialScrollFactor = { value: [0, 10] }; + }); + it('should update pageYShiftFactor for iOS when location.y is 0', async () => { + await provider.updatePageShiftFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(-10); + }); + + it('should not update pageYShiftFactor for iOS when location.y is not 0', async () => { + // Location.y is not 0 + await provider.updatePageShiftFactor({ y: 5 }); + expect(provider.pageYShiftFactor).toBe(0); + }); }); - it('should not update pageYShiftFactor for iOS when location.y is not 0', async () => { - // Location.y is not 0 - await provider.updatePageShiftFactor({ y: 5 }); - expect(provider.pageYShiftFactor).toBe(0); + describe('when element is not visible in viewport and iOS scrolls automatically', () => { + beforeEach(() => { + provider.initialScrollFactor = { value: [0, 30] }; + }); + it('should update pageYShiftFactor to negative value even if location.y is 0', async () => { + await provider.updatePageShiftFactor({ y: 0 }); + expect(provider.pageYShiftFactor).toBe(-50000); + }); + + it('should update pageYShiftFactor to negative value even if location.y is not 0', async () => { + // Location.y is not 0 + await provider.updatePageShiftFactor({ y: 5 }); + expect(provider.pageYShiftFactor).toBe(-50000); + }); }); }); @@ -384,34 +405,88 @@ describe('GenericProvider', () => { }); }); + describe('getRegionObjectFromBoundingBox', () => { + let provider; + let mockLocation = { x: 10, y: 20, width: 100, height: 200 }; + beforeEach(async () => { + // mock metadata + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + spyOn(DesktopMetaData.prototype, 'devicePixelRatio') + .and.returnValue(1); + provider.statusBarHeight = 0; + }); + + describe('When not an iOS', () => { + it('should return a JSON object with the correct selector and coordinates', async () => { + // Call function with mock data + const selector = 'mock-selector'; + const result = await provider.getRegionObjectFromBoundingBox(selector, mockLocation); + + // Assert expected result + expect(result.selector).toEqual(selector); + expect(result.coOrdinates).toEqual({ + top: mockLocation.y, + bottom: mockLocation.y + mockLocation.height, + left: mockLocation.x, + right: mockLocation.x + mockLocation.width + }); + }); + }); + + describe('When iOS', () => { + beforeEach(() => { + provider.currentOperatingSystem = 'iOS'; + provider.statusBarHeight = 132; + }); + it('should return a JSON object with the correct selector and coordinates with added statusBarHeight', async () => { + await provider.createDriver(); + + // Call function with mock data + const selector = 'mock-selector'; + const result = await provider.getRegionObjectFromBoundingBox(selector, mockLocation); + + // Assert expected result + expect(result.selector).toEqual(selector); + expect(result.coOrdinates).toEqual({ + top: mockLocation.y + provider.statusBarHeight, + bottom: mockLocation.y + mockLocation.height + provider.statusBarHeight, + left: mockLocation.x, + right: mockLocation.x + mockLocation.width + }); + }); + }); + }); + describe('getSeleniumRegionsByXpaths', () => { let getRegionObjectSpy; let provider; + let xpathResponse = { top: 0, bottom: 500, right: 0, left: 300 }; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); - getRegionObjectSpy = spyOn(GenericProvider.prototype, 'getRegionObject').and.returnValue({}); + getRegionObjectSpy = spyOn(GenericProvider.prototype, 'getRegionObjectFromBoundingBox').and.returnValue(xpathResponse); }); it('should add regions for each xpath', async () => { - spyOn(Driver.prototype, 'findElement').and.returnValue(Promise.resolve({ ELEMENT: 'mock_id' })); + spyOn(Driver.prototype, 'findElementBoundingBox').and.returnValue(Promise.resolve({ x: 0, y: 100, height: 500, width: 300 })); const xpaths = ['/xpath/1', '/xpath/2', '/xpath/3']; const elementsArray = await provider.getSeleniumRegionsBy('xpath', xpaths); - expect(provider.driver.findElement).toHaveBeenCalledTimes(3); + expect(provider.driver.findElementBoundingBox).toHaveBeenCalledTimes(3); expect(getRegionObjectSpy).toHaveBeenCalledTimes(3); - expect(elementsArray).toEqual([{}, {}, {}]); + expect(elementsArray).toEqual([xpathResponse, xpathResponse, xpathResponse]); }); it('should ignore xpath when element is not found', async () => { - spyOn(Driver.prototype, 'findElement').and.rejectWith(new Error('Element not found')); + spyOn(Driver.prototype, 'findElementBoundingBox').and.rejectWith(new Error('Element not found')); const xpaths = ['/xpath/1', '/xpath/2', '/xpath/3']; const elementsArray = await provider.getSeleniumRegionsBy('xpath', xpaths); - expect(provider.driver.findElement).toHaveBeenCalledTimes(3); + expect(provider.driver.findElementBoundingBox).toHaveBeenCalledTimes(3); expect(getRegionObjectSpy).not.toHaveBeenCalled(); expect(elementsArray).toEqual([]); }); @@ -424,48 +499,118 @@ describe('GenericProvider', () => { beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); - getRegionObjectSpy = spyOn(GenericProvider.prototype, 'getRegionObject').and.returnValue({}); + getRegionObjectSpy = spyOn(GenericProvider.prototype, 'getRegionObjectFromBoundingBox').and.returnValue({}); }); it('should add regions for each id', async () => { - spyOn(Driver.prototype, 'findElement').and.returnValue(Promise.resolve({ ELEMENT: 'mock_id' })); + spyOn(Driver.prototype, 'findElementBoundingBox').and.returnValue(Promise.resolve({ value: { x: 0, y: 100, height: 500, width: 300 } })); const ids = ['#id1', '#id2', '#id3']; const elementsArray = await provider.getSeleniumRegionsBy('css selector', ids); - expect(provider.driver.findElement).toHaveBeenCalledTimes(3); + expect(provider.driver.findElementBoundingBox).toHaveBeenCalledTimes(3); expect(getRegionObjectSpy).toHaveBeenCalledTimes(3); expect(elementsArray).toEqual([{}, {}, {}]); }); it('should ignore id when element is not found', async () => { - spyOn(Driver.prototype, 'findElement').and.rejectWith(new Error('Element not found')); + spyOn(Driver.prototype, 'findElementBoundingBox').and.rejectWith(new Error('Element not found')); const ids = ['#id1', '#id2', '#id3']; const elementsArray = await provider.getSeleniumRegionsBy('css selector', ids); - expect(provider.driver.findElement).toHaveBeenCalledTimes(3); + expect(provider.driver.findElementBoundingBox).toHaveBeenCalledTimes(3); expect(getRegionObjectSpy).not.toHaveBeenCalled(); expect(elementsArray).toEqual([]); }); }); + describe('getInitialPosition', () => { + let provider; + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + }); + describe('when not IOS', () => { + it('should not get the initial scroll position', async () => { + await provider.getInitialPosition(); + expect(provider.initialScrollFactor).toEqual({ value: [0, 0] }); + }); + }); + + describe('when IOS', () => { + let executeScriptSpy; + beforeEach(() => { + provider.currentOperatingSystem = 'iOS'; + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + }); + + afterEach(() => { + provider.currentOperatingSystem = null; + }); + it('should get the initial scroll position', async () => { + spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 200] }); + await provider.getInitialPosition(); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + expect(provider.initialScrollFactor).toEqual({ value: [0, 200] }); + }); + }); + }); + + describe('scrollToInitialPosition', () => { + let provider; + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + }); + describe('when not IOS', () => { + it('should not scroll to position', async () => { + await provider.scrollToInitialPosition(0, 50); + expect(spyOn(Driver.prototype, 'executeScript')).toHaveBeenCalledTimes(0); + }); + }); + + describe('when IOS', () => { + let executeScriptSpy; + beforeEach(() => { + provider.currentOperatingSystem = 'iOS'; + provider.initialScrollFactor = { value: [0, 50] }; + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + }); + + afterEach(() => { + provider.currentOperatingSystem = null; + }); + it('should scroll to position', async () => { + await provider.scrollToInitialPosition(0, 50); + expect(executeScriptSpy).toHaveBeenCalledTimes(1); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'window.scrollTo(0, 50)', args: [] }); + }); + }); + }); + describe('getSeleniumRegionsByElement', () => { let getRegionObjectSpy; + let getInitialPositionSpy; + let scrollToInitialPositionSpy; let provider; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); getRegionObjectSpy = spyOn(GenericProvider.prototype, 'getRegionObject').and.returnValue({}); + getInitialPositionSpy = spyOn(GenericProvider.prototype, 'getInitialPosition'); + scrollToInitialPositionSpy = spyOn(GenericProvider.prototype, 'scrollToInitialPosition'); }); it('should add regions for each element', async () => { const elements = ['mockElement_1', 'mockElement_2', 'mockElement_3']; const elementsArray = await provider.getSeleniumRegionsByElement(elements); - + expect(getInitialPositionSpy).toHaveBeenCalledTimes(1); expect(getRegionObjectSpy).toHaveBeenCalledTimes(3); + expect(scrollToInitialPositionSpy).toHaveBeenCalledTimes(1); + expect(scrollToInitialPositionSpy).toHaveBeenCalledWith(provider.initialScrollFactor.value[0], provider.initialScrollFactor.value[1]); expect(elementsArray).toEqual([{}, {}, {}]); }); From a9709d5189059d1ac914be0fa353b564a91d5789 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 1 Nov 2023 06:13:43 +0530 Subject: [PATCH 08/13] Formatting the PR little bit --- .../src/providers/genericProvider.js | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index 6f3adab65..dd99b86c0 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -69,6 +69,18 @@ export default class GenericProvider { } } + async getInitialPosition() { + if (this.currentOperatingSystem === 'iOS') { + this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + } + } + + async scrollToInitialPosition(x, y) { + if (this.currentOperatingSystem === 'iOS') { + await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); + } + } + async screenshot(name, { ignoreRegionXpaths = [], ignoreRegionSelectors = [], @@ -195,45 +207,6 @@ export default class GenericProvider { ]; } - async updatePageShiftFactor(location) { - const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); - this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); - this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); - if (this.currentOperatingSystem === 'iOS') { - if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { - this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); - this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); - } else if (location.y === 0) { - this.pageYShiftFactor += (-scrollFactors.value[1]); - } - } - } - - async getRegionObject(selector, elementId) { - const scaleFactor = await this.metaData.devicePixelRatio(); - const rect = await this.driver.rect(elementId); - const location = { x: rect.x, y: rect.y }; - const size = { height: rect.height, width: rect.width }; - // Update pageShiftFactor Element is not visible in viewport - // In case of iOS if the element is not visible in viewport it gives 0 for x-y coordinate. - // In case of iOS if the element is partially visible it gives negative x-y coordinate. - // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not. - await this.updatePageShiftFactor(location); - const coOrdinates = { - top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, - bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, - left: Math.floor(location.x * scaleFactor) + this.pageXShiftFactor, - right: Math.ceil((location.x + size.width) * scaleFactor) + this.pageXShiftFactor - }; - - const jsonObject = { - selector, - coOrdinates - }; - - return jsonObject; - } - async getRegionObjectFromBoundingBox(selector, element) { const scaleFactor = await this.metaData.devicePixelRatio(); let headerAdjustment = 0; @@ -271,16 +244,43 @@ export default class GenericProvider { return regionsArray; } - async getInitialPosition() { + async updatePageShiftFactor(location) { + const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); + this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); if (this.currentOperatingSystem === 'iOS') { - this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { + this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); + this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); + } else if (location.y === 0) { + this.pageYShiftFactor += (-scrollFactors.value[1]); + } } } - async scrollToInitialPosition(x, y) { - if (this.currentOperatingSystem === 'iOS') { - await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); - } + async getRegionObject(selector, elementId) { + const scaleFactor = await this.metaData.devicePixelRatio(); + const rect = await this.driver.rect(elementId); + const location = { x: rect.x, y: rect.y }; + const size = { height: rect.height, width: rect.width }; + // Update pageShiftFactor Element is not visible in viewport + // In case of iOS if the element is not visible in viewport it gives 0 for x-y coordinate. + // In case of iOS if the element is partially visible it gives negative x-y coordinate. + // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not. + await this.updatePageShiftFactor(location); + const coOrdinates = { + top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, + bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, + left: Math.floor(location.x * scaleFactor) + this.pageXShiftFactor, + right: Math.ceil((location.x + size.width) * scaleFactor) + this.pageXShiftFactor + }; + + const jsonObject = { + selector, + coOrdinates + }; + + return jsonObject; } async getSeleniumRegionsByElement(elements) { From b126c0a7e29d2df87b1ad48018a28354f029b5a7 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Sat, 4 Nov 2023 02:54:46 +0530 Subject: [PATCH 09/13] Updating Tests and Refactoring --- .../src/providers/genericProvider.js | 14 +++++++------- .../test/providers/genericProvider.test.js | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index dd99b86c0..c40449ad5 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -71,7 +71,7 @@ export default class GenericProvider { async getInitialPosition() { if (this.currentOperatingSystem === 'iOS') { - this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); } } @@ -244,16 +244,16 @@ export default class GenericProvider { return regionsArray; } - async updatePageShiftFactor(location) { - const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); - this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - scrollFactors.value[1]); - this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-scrollFactors.value[0]); + async updatePageShiftFactor(location, scaleFactor) { + const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); + this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - (scrollFactors.value[1] * scaleFactor)); + this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-(scrollFactors.value[0] * scaleFactor)); if (this.currentOperatingSystem === 'iOS') { if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); } else if (location.y === 0) { - this.pageYShiftFactor += (-scrollFactors.value[1]); + this.pageYShiftFactor += (-(scrollFactors.value[1] * scaleFactor)); } } } @@ -267,7 +267,7 @@ export default class GenericProvider { // In case of iOS if the element is not visible in viewport it gives 0 for x-y coordinate. // In case of iOS if the element is partially visible it gives negative x-y coordinate. // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not. - await this.updatePageShiftFactor(location); + await this.updatePageShiftFactor(location, scaleFactor); const coOrdinates = { top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index e4ac57e44..0e44dcdcb 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -282,13 +282,13 @@ describe('GenericProvider', () => { provider.initialScrollFactor = { value: [0, 10] }; }); it('should update pageYShiftFactor for iOS when location.y is 0', async () => { - await provider.updatePageShiftFactor({ y: 0 }); - expect(provider.pageYShiftFactor).toBe(-10); + await provider.updatePageShiftFactor({ y: 0 }, 2); + expect(provider.pageYShiftFactor).toBe(-20); }); it('should not update pageYShiftFactor for iOS when location.y is not 0', async () => { // Location.y is not 0 - await provider.updatePageShiftFactor({ y: 5 }); + await provider.updatePageShiftFactor({ y: 5 }, 2); expect(provider.pageYShiftFactor).toBe(0); }); }); @@ -298,13 +298,13 @@ describe('GenericProvider', () => { provider.initialScrollFactor = { value: [0, 30] }; }); it('should update pageYShiftFactor to negative value even if location.y is 0', async () => { - await provider.updatePageShiftFactor({ y: 0 }); + await provider.updatePageShiftFactor({ y: 0 }, 2); expect(provider.pageYShiftFactor).toBe(-50000); }); it('should update pageYShiftFactor to negative value even if location.y is not 0', async () => { // Location.y is not 0 - await provider.updatePageShiftFactor({ y: 5 }); + await provider.updatePageShiftFactor({ y: 5 }, 2); expect(provider.pageYShiftFactor).toBe(-50000); }); }); @@ -320,13 +320,13 @@ describe('GenericProvider', () => { it('should not update pageYShiftFactor for non-iOS platforms', async () => { spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 0] }); - await provider.updatePageShiftFactor({ y: 0 }); + await provider.updatePageShiftFactor({ y: 0 }, 1); expect(provider.pageYShiftFactor).toBe(0); }); it('should update pageYShiftFactor for non-iOS platforms accordingly if scrolled', async () => { spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); - await provider.updatePageShiftFactor({ y: 0 }); + await provider.updatePageShiftFactor({ y: 0 }, 1); expect(provider.pageYShiftFactor).toBe(-10); }); }); @@ -547,7 +547,7 @@ describe('GenericProvider', () => { it('should get the initial scroll position', async () => { spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 200] }); await provider.getInitialPosition(); - expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'return [parseInt(window.scrollX * window.devicePixelRatio), parseInt(window.scrollY * window.devicePixelRatio)];', args: [] }); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); expect(provider.initialScrollFactor).toEqual({ value: [0, 200] }); }); }); From b79c1a003a7826e9e34b8930fa0b0b41dbd6f5e9 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Tue, 14 Nov 2023 19:23:04 +0530 Subject: [PATCH 10/13] Updating Specs and Handling Safari Specific cases --- packages/webdriver-utils/src/driver.js | 1 + .../src/providers/genericProvider.js | 20 +++--- .../test/providers/genericProvider.test.js | 61 ++++++++++++++++--- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/packages/webdriver-utils/src/driver.js b/packages/webdriver-utils/src/driver.js index bcadfc4a7..2bdd428e7 100644 --- a/packages/webdriver-utils/src/driver.js +++ b/packages/webdriver-utils/src/driver.js @@ -87,6 +87,7 @@ export default class Driver { } async findElementXpath(xpath) { + xpath = xpath.replace(/'/g, '"'); const command = { script: `return document.evaluate('${xpath}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect();`, args: [] }; const response = await this.executeScript(command); return response.value; diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index c40449ad5..61922f03a 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -36,7 +36,7 @@ export default class GenericProvider { this.statusBarHeight = 0; this.pageXShiftFactor = 0; this.pageYShiftFactor = 0; - this.currentOperatingSystem = null; + this.currentTag = null; this.removeElementShiftFactor = 50000; this.initialScrollFactor = { value: [0, 0] }; } @@ -70,13 +70,13 @@ export default class GenericProvider { } async getInitialPosition() { - if (this.currentOperatingSystem === 'iOS') { + if (this.currentTag.osName === 'iOS') { this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); } } async scrollToInitialPosition(x, y) { - if (this.currentOperatingSystem === 'iOS') { + if (this.currentTag.osName === 'iOS') { await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); } } @@ -104,7 +104,7 @@ export default class GenericProvider { const tiles = await this.getTiles(this.header, this.footer, fullscreen); log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); - this.currentOperatingSystem = tag.osName; + this.currentTag = tag; this.statusBarHeight = tiles.tiles[0].statusBarHeight; const ignoreRegions = await this.findRegions( @@ -210,7 +210,7 @@ export default class GenericProvider { async getRegionObjectFromBoundingBox(selector, element) { const scaleFactor = await this.metaData.devicePixelRatio(); let headerAdjustment = 0; - if (this.currentOperatingSystem === 'iOS') { + if (this.currentTag.osName === 'iOS') { headerAdjustment = this.statusBarHeight; } const coOrdinates = { @@ -246,9 +246,13 @@ export default class GenericProvider { async updatePageShiftFactor(location, scaleFactor) { const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); - this.pageYShiftFactor = this.currentOperatingSystem === 'iOS' ? this.statusBarHeight : (this.statusBarHeight - (scrollFactors.value[1] * scaleFactor)); - this.pageXShiftFactor = this.currentOperatingSystem === 'iOS' ? 0 : (-(scrollFactors.value[0] * scaleFactor)); - if (this.currentOperatingSystem === 'iOS') { + if (this.currentTag.osName === 'iOS' || (this.currentTag.osName === 'OS X' && parseInt(this.currentTag.browserVersion) > 13 && this.currentTag.browserName.toLowerCase() === 'safari')) { + this.pageYShiftFactor = this.statusBarHeight; + } else { + this.pageYShiftFactor = this.statusBarHeight - (scrollFactors.value[1] * scaleFactor); + } + this.pageXShiftFactor = this.currentTag.osName === 'iOS' ? 0 : (-(scrollFactors.value[0] * scaleFactor)); + if (this.currentTag.osName === 'iOS') { if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) { this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 0e44dcdcb..822b94437 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -272,7 +272,7 @@ describe('GenericProvider', () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); - provider.currentOperatingSystem = 'iOS'; + provider.currentTag = { osName: 'iOS' }; provider.pageYShiftFactor = 0; provider.statusBarHeight = 0; }); @@ -310,11 +310,52 @@ describe('GenericProvider', () => { }); }); + describe('When OS X', () => { + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'OS X' }, {}); + await provider.createDriver(); + spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); + provider.currentTag = { osName: 'OS X' }; + provider.pageYShiftFactor = 0; + provider.statusBarHeight = 0; + }); + + describe('When Safari browserVersion > 13', () => { + describe('when element is visible in viewport', () => { + beforeEach(() => { + provider.initialScrollFactor = { value: [0, 10] }; + provider.currentTag.browserName = 'safari'; + provider.currentTag.browserVersion = 15; + }); + + it('should not update pageYShiftFactor for OS X if scrolled', async () => { + await provider.updatePageShiftFactor({ y: 0 }, 1); + expect(provider.pageYShiftFactor).toBe(0); + }); + }); + }); + + describe('When Safari browserVersion <= 13', () => { + describe('when element is visible in viewport', () => { + beforeEach(() => { + provider.initialScrollFactor = { value: [0, 10] }; + provider.currentTag.browserName = 'safari'; + provider.currentTag.browserVersion = 13; + }); + + it('should update pageYShiftFactor for OS X platforms accordingly if scrolled', async () => { + await provider.updatePageShiftFactor({ y: 0 }, 1); + expect(provider.pageYShiftFactor).toBe(-10); + }); + }); + }); + }); + describe('When Other', () => { beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); - provider.currentOperatingSystem = 'Android'; + provider.currentTag = { osName: 'Android' }; provider.pageYShiftFactor = 0; }); @@ -340,6 +381,7 @@ describe('GenericProvider', () => { beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentTag = { osName: 'Windows' }; await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); @@ -370,6 +412,7 @@ describe('GenericProvider', () => { beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentTag = { osName: 'iOS' }; await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); @@ -381,6 +424,7 @@ describe('GenericProvider', () => { afterEach(() => { provider.pageYShiftFactor = 0; + provider.currentTag = null; }); it('should return a JSON object with the correct selector and coordinates', async () => { await provider.createDriver(); @@ -407,6 +451,7 @@ describe('GenericProvider', () => { beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentTag = { osName: 'Windows' }; await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); @@ -432,7 +477,7 @@ describe('GenericProvider', () => { describe('When iOS', () => { beforeEach(() => { - provider.currentOperatingSystem = 'iOS'; + provider.currentTag = { osName: 'iOS' }; provider.statusBarHeight = 132; }); it('should return a JSON object with the correct selector and coordinates with added statusBarHeight', async () => { @@ -525,6 +570,7 @@ describe('GenericProvider', () => { let provider; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentTag = { osName: 'Windows' }; await provider.createDriver(); }); describe('when not IOS', () => { @@ -537,12 +583,12 @@ describe('GenericProvider', () => { describe('when IOS', () => { let executeScriptSpy; beforeEach(() => { - provider.currentOperatingSystem = 'iOS'; + provider.currentTag = { osName: 'iOS' }; executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); }); afterEach(() => { - provider.currentOperatingSystem = null; + provider.currentTag = null; }); it('should get the initial scroll position', async () => { spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 200] }); @@ -557,6 +603,7 @@ describe('GenericProvider', () => { let provider; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + provider.currentTag = { osName: 'Windows' }; await provider.createDriver(); }); describe('when not IOS', () => { @@ -569,13 +616,13 @@ describe('GenericProvider', () => { describe('when IOS', () => { let executeScriptSpy; beforeEach(() => { - provider.currentOperatingSystem = 'iOS'; + provider.currentTag = { osName: 'iOS' }; provider.initialScrollFactor = { value: [0, 50] }; executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); }); afterEach(() => { - provider.currentOperatingSystem = null; + provider.currentTag = null; }); it('should scroll to position', async () => { await provider.scrollToInitialPosition(0, 50); From 0bfaad1cb0b6ec4445fb75055f68047665b99c95 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Tue, 21 Nov 2023 19:18:03 +0530 Subject: [PATCH 11/13] Adding Floor & Ceil to shifter variables --- .../webdriver-utils/src/providers/genericProvider.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index 61922f03a..8cbe4b3d2 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -214,8 +214,8 @@ export default class GenericProvider { headerAdjustment = this.statusBarHeight; } const coOrdinates = { - top: Math.floor(element.y * scaleFactor) + headerAdjustment, - bottom: Math.ceil((element.y + element.height) * scaleFactor) + headerAdjustment, + top: Math.floor(element.y * scaleFactor) + Math.floor(headerAdjustment), + bottom: Math.ceil((element.y + element.height) * scaleFactor) + Math.ceil(headerAdjustment), left: Math.floor(element.x * scaleFactor), right: Math.ceil((element.x + element.width) * scaleFactor) }; @@ -273,10 +273,10 @@ export default class GenericProvider { // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not. await this.updatePageShiftFactor(location, scaleFactor); const coOrdinates = { - top: Math.floor(location.y * scaleFactor) + this.pageYShiftFactor, - bottom: Math.ceil((location.y + size.height) * scaleFactor) + this.pageYShiftFactor, - left: Math.floor(location.x * scaleFactor) + this.pageXShiftFactor, - right: Math.ceil((location.x + size.width) * scaleFactor) + this.pageXShiftFactor + top: Math.floor(location.y * scaleFactor) + Math.floor(this.pageYShiftFactor), + bottom: Math.ceil((location.y + size.height) * scaleFactor) + Math.ceil(this.pageYShiftFactor), + left: Math.floor(location.x * scaleFactor) + Math.floor(this.pageXShiftFactor), + right: Math.ceil((location.x + size.width) * scaleFactor) + Math.ceil(this.pageXShiftFactor) }; const jsonObject = { From 49b9db7b854c4dbbee39d72e55190201a97c9233 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 22 Nov 2023 19:06:47 +0530 Subject: [PATCH 12/13] Handling Scrollbar transformation for ignore-region --- .../src/providers/genericProvider.js | 58 ++++++++-- .../test/providers/genericProvider.test.js | 103 ++++++++++++++++++ 2 files changed, 150 insertions(+), 11 deletions(-) diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index 8cbe4b3d2..dbd548447 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -193,18 +193,54 @@ export default class GenericProvider { this.debugUrl = 'https://localhost/v1'; } + async doTransformations() { + const hideScrollbarStyle = ` + /* Hide scrollbar for Chrome, Safari and Opera */ + ::-webkit-scrollbar { + display: none !important; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + body, html { + -ms-overflow-style: none !important; /* IE and Edge */ + scrollbar-width: none !important; /* Firefox */ + }`.replace(/\n/g, ''); + const jsScript = ` + const e = document.createElement('style'); + e.setAttribute('class', 'poa-injected'); + e.innerHTML = '${hideScrollbarStyle}' + document.head.appendChild(e);`; + + await this.driver.executeScript({ script: jsScript, args: [] }); + } + + async undoTransformations(data) { + const jsScript = ` + const n = document.querySelectorAll('${data}'); + n.forEach((e) => {e.remove()});`; + + await this.driver.executeScript({ script: jsScript, args: [] }); + } + async findRegions(xpaths, selectors, elements, customLocations) { - const xpathRegions = await this.getSeleniumRegionsBy('xpath', xpaths); - const selectorRegions = await this.getSeleniumRegionsBy('css selector', selectors); - const elementRegions = await this.getSeleniumRegionsByElement(elements); - const customRegions = await this.getSeleniumRegionsByLocation(customLocations); - - return [ - ...xpathRegions, - ...selectorRegions, - ...elementRegions, - ...customRegions - ]; + let isRegionPassed = [xpaths, selectors, elements, customLocations].some(regions => regions.length > 0); + if (isRegionPassed) { + await this.doTransformations(); + const xpathRegions = await this.getSeleniumRegionsBy('xpath', xpaths); + const selectorRegions = await this.getSeleniumRegionsBy('css selector', selectors); + const elementRegions = await this.getSeleniumRegionsByElement(elements); + const customRegions = await this.getSeleniumRegionsByLocation(customLocations); + await this.undoTransformations('.poa-injected'); + + return [ + ...xpathRegions, + ...selectorRegions, + ...elementRegions, + ...customRegions + ]; + } else { + return []; + } } async getRegionObjectFromBoundingBox(selector, element) { diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 822b94437..144bd9e37 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -708,4 +708,107 @@ describe('GenericProvider', () => { expect(elementsArray).toEqual([]); }); }); + + describe('findRegions', () => { + let provider; + let doTransformationSpy; + let undoTransformationSpy; + let getSeleniumRegionsBySpy; + let getSeleniumRegionsByElementSpy; + let getSeleniumRegionsByLocationSpy; + const location = [ + { top: 100, bottom: 1090, left: 100, right: 200 }, + ]; + + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + doTransformationSpy = spyOn(GenericProvider.prototype, 'doTransformations'); + undoTransformationSpy = spyOn(GenericProvider.prototype, 'undoTransformations'); + getSeleniumRegionsBySpy = spyOn(GenericProvider.prototype, 'getSeleniumRegionsBy').and.returnValue(Promise.resolve(location)); + getSeleniumRegionsByElementSpy = spyOn(GenericProvider.prototype, 'getSeleniumRegionsByElement').and.returnValue(Promise.resolve([])); + getSeleniumRegionsByLocationSpy = spyOn(GenericProvider.prototype, 'getSeleniumRegionsByLocation').and.returnValue(Promise.resolve([])); + }); + + describe('When no regions are passed', () => { + it('should return empty array when called and no transformation should be applied', async () => { + const xpath = []; + const selector = []; + const seleniumElements = []; + const customRegions = []; + const res = await provider.findRegions(xpath, selector, seleniumElements, customRegions); + expect(doTransformationSpy).not.toHaveBeenCalled(); + expect(undoTransformationSpy).not.toHaveBeenCalled(); + expect(res).toEqual([]); + }); + }); + + describe('When regions are passed', () => { + it('should return array when called transformation should be applied', async () => { + const xpath = ['/a/b/c']; + const selector = []; + const seleniumElements = []; + const customRegions = []; + await provider.findRegions(xpath, selector, seleniumElements, customRegions); + expect(doTransformationSpy).toHaveBeenCalled(); + expect(getSeleniumRegionsBySpy).toHaveBeenCalledTimes(2); + expect(getSeleniumRegionsByElementSpy).toHaveBeenCalledTimes(1); + expect(getSeleniumRegionsByLocationSpy).toHaveBeenCalledTimes(1); + expect(undoTransformationSpy).toHaveBeenCalled(); + }); + }); + }); + + describe('doTransformations', () => { + let provider; + let executeScriptSpy; + + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + }); + + it('should do transfomation', async () => { + const hideScrollbarStyle = ` + /* Hide scrollbar for Chrome, Safari and Opera */ + ::-webkit-scrollbar { + display: none !important; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + body, html { + -ms-overflow-style: none !important; /* IE and Edge */ + scrollbar-width: none !important; /* Firefox */ + }`.replace(/\n/g, ''); + const jsScript = ` + const e = document.createElement('style'); + e.setAttribute('class', 'poa-injected'); + e.innerHTML = '${hideScrollbarStyle}' + document.head.appendChild(e);`; + await provider.doTransformations(); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: jsScript, args: [] }); + }); + }); + + describe('undoTransformations', () => { + let provider; + let executeScriptSpy; + + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + }); + + it('should remove transfomation', async () => { + const data = '.abcdef'; + const jsScript = ` + const n = document.querySelectorAll('${data}'); + n.forEach((e) => {e.remove()});`; + + await provider.undoTransformations(data); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: jsScript, args: [] }); + }); + }); }); From b30e4c1c2a79ed9e786ddd9d9c79f0d63d7fdedc Mon Sep 17 00:00:00 2001 From: amit3200 Date: Wed, 22 Nov 2023 19:09:51 +0530 Subject: [PATCH 13/13] Fixing lint issue --- packages/webdriver-utils/test/providers/genericProvider.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 144bd9e37..8f34ce56e 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -717,7 +717,7 @@ describe('GenericProvider', () => { let getSeleniumRegionsByElementSpy; let getSeleniumRegionsByLocationSpy; const location = [ - { top: 100, bottom: 1090, left: 100, right: 200 }, + { top: 100, bottom: 1090, left: 100, right: 200 } ]; beforeEach(async () => {