diff --git a/packages/client/test/client.test.js b/packages/client/test/client.test.js index 39b827532..478322bdf 100644 --- a/packages/client/test/client.test.js +++ b/packages/client/test/client.test.js @@ -861,7 +861,8 @@ describe('PercyClient', () => { consideredElementsData: consideredElementsData, domInfoSha: 'abcd=', metadata: { - windowHeight: 1947 + windowHeight: 1947, + screenshotType: 'singlepage' } })).toBeResolved(); @@ -874,7 +875,8 @@ describe('PercyClient', () => { 'consider-elements-data': consideredElementsData, 'dom-info-sha': 'abcd=', metadata: { - windowHeight: 1947 + windowHeight: 1947, + screenshotType: 'singlepage' } }, relationships: { @@ -1010,6 +1012,7 @@ describe('PercyClient', () => { externalDebugUrl: 'https://automate.browserstack.com/builds/acs', metadata: { windowHeight: 1947, + screenshotType: 'fullpage', abc: 123 } }; diff --git a/packages/core/src/config.js b/packages/core/src/config.js index 43eee4aae..f90dfc068 100644 --- a/packages/core/src/config.js +++ b/packages/core/src/config.js @@ -545,7 +545,8 @@ export const comparisonSchema = { minimum: 0 }, cliScreenshotStartTime: { type: 'integer', default: 0 }, - cliScreenshotEndTime: { type: 'integer', default: 0 } + cliScreenshotEndTime: { type: 'integer', default: 0 }, + screenshotType: { type: 'string', default: 'singlepage' } } }, tag: { diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index cdb190243..0c69c213b 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -48,7 +48,8 @@ export function percyAutomateRequestHandler(req, percy) { ignoreRegionSelectors: percy.config.snapshot.ignoreRegions?.ignoreRegionSelectors, ignoreRegionXpaths: percy.config.snapshot.ignoreRegions?.ignoreRegionXpaths, considerRegionSelectors: percy.config.snapshot.considerRegions?.considerRegionSelectors, - considerRegionXpaths: percy.config.snapshot.considerRegions?.considerRegionXpaths + considerRegionXpaths: percy.config.snapshot.considerRegions?.considerRegionXpaths, + version: 'v2' }, camelCasedOptions ], (path, prev, next) => { diff --git a/packages/core/test/api.test.js b/packages/core/test/api.test.js index d0a5a620d..8a3f6493b 100644 --- a/packages/core/test/api.test.js +++ b/packages/core/test/api.test.js @@ -307,7 +307,8 @@ describe('API Server', () => { percyCSS: '.global { color: blue }\n.percy-screenshot: { color: red }', ignoreRegionSelectors: ['.selector-global'], ignoreRegionXpaths: ['/xpath-per-screenshot'], - considerRegionXpaths: ['/xpath-global', '/xpath-per-screenshot'] + considerRegionXpaths: ['/xpath-global', '/xpath-per-screenshot'], + version: 'v2' } })); diff --git a/packages/webdriver-utils/src/providers/automateProvider.js b/packages/webdriver-utils/src/providers/automateProvider.js index 7985b951a..5f84393d7 100644 --- a/packages/webdriver-utils/src/providers/automateProvider.js +++ b/packages/webdriver-utils/src/providers/automateProvider.js @@ -123,15 +123,16 @@ export default class AutomateProvider extends GenericProvider { }); } - async getTiles(headerHeight, footerHeight, fullscreen) { + async getTiles(fullscreen) { if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().'); log.debug('Starting actual screenshotting phase'); const dpr = await this.metaData.devicePixelRatio(); + const screenshotType = this.options?.fullPage ? 'fullpage' : 'singlepage'; const response = await TimeIt.run('percyScreenshot:screenshot', async () => { return await this.browserstackExecutor('percyScreenshot', { state: 'screenshot', percyBuildId: this.buildInfo.id, - screenshotType: 'singlepage', + screenshotType: screenshotType, scaleFactor: dpr, options: this.options }); @@ -146,20 +147,18 @@ export default class AutomateProvider extends GenericProvider { const tiles = []; const tileResponse = JSON.parse(responseValue.result); log.debug('Tiles captured successfully'); - const windowHeight = responseValue?.metadata?.window_height || 0; - for (let tileData of tileResponse.sha) { + for (let tileData of tileResponse.tiles) { tiles.push(new Tile({ - statusBarHeight: tileResponse.header_height || 0, - navBarHeight: tileResponse.footer_height || 0, - headerHeight, - footerHeight, + statusBarHeight: tileData.status_bar || 0, + navBarHeight: tileData.nav_bar || 0, + headerHeight: tileData.header_height || 0, + footerHeight: tileData.footer_height || 0, fullscreen, - sha: tileData.split('-')[0] // drop build id + sha: tileData.sha.split('-')[0] // drop build id })); } - const metadata = { - windowHeight: Math.ceil(windowHeight * dpr) + screenshotType: screenshotType }; return { tiles: tiles, domInfoSha: tileResponse.dom_sha, metadata: metadata }; } diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index dbd548447..5db5cad35 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -38,7 +38,7 @@ export default class GenericProvider { this.pageYShiftFactor = 0; this.currentTag = null; this.removeElementShiftFactor = 50000; - this.initialScrollFactor = { value: [0, 0] }; + this.initialScrollLocation = null; } addDefaultOptions() { @@ -69,16 +69,24 @@ export default class GenericProvider { } } - async getInitialPosition() { - if (this.currentTag.osName === 'iOS') { - this.initialScrollFactor = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); - } + isIOS() { + return this.currentTag?.osName === 'iOS'; + } + + async getScrollDetails() { + return await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); } - async scrollToInitialPosition(x, y) { - if (this.currentTag.osName === 'iOS') { - await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); + async getInitialScrollLocation() { + if (this.initialScrollLocation) { + return this.initialScrollLocation; } + this.initialScrollLocation = await this.getScrollDetails(); + return this.initialScrollLocation; + } + + async scrollToPosition(x, y) { + await this.driver.executeScript({ script: `window.scrollTo(${x}, ${y})`, args: [] }); } async screenshot(name, { @@ -101,7 +109,7 @@ export default class GenericProvider { const tag = await this.getTag(); log.debug(`[${name}] : Tag ${JSON.stringify(tag)}`); - const tiles = await this.getTiles(this.header, this.footer, fullscreen); + const tiles = await this.getTiles(fullscreen); log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); this.currentTag = tag; @@ -146,7 +154,7 @@ export default class GenericProvider { return await this.driver.executeScript({ script: 'return window.innerHeight', args: [] }); ; } - async getTiles(headerHeight, footerHeight, fullscreen) { + async getTiles(fullscreen) { if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().'); const base64content = await this.driver.takeScreenshot(); log.debug('Tiles captured successfully'); @@ -156,8 +164,8 @@ export default class GenericProvider { content: base64content, statusBarHeight: 0, navBarHeight: 0, - headerHeight, - footerHeight, + headerHeight: this.header, + footerHeight: this.footer, fullscreen }) ], @@ -212,6 +220,9 @@ export default class GenericProvider { document.head.appendChild(e);`; await this.driver.executeScript({ script: jsScript, args: [] }); + if (this.options?.fullPage || this.isIOS()) { + await this.getInitialScrollLocation(); + } } async undoTransformations(data) { @@ -245,15 +256,21 @@ export default class GenericProvider { async getRegionObjectFromBoundingBox(selector, element) { const scaleFactor = await this.metaData.devicePixelRatio(); + let scrollX = 0, scrollY = 0; + if (this.options?.fullPage) { + scrollX = this.initialScrollLocation.value[0]; + scrollY = this.initialScrollLocation.value[1]; + } + let headerAdjustment = 0; - if (this.currentTag.osName === 'iOS') { + if (this.isIOS()) { headerAdjustment = this.statusBarHeight; } const coOrdinates = { - 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) + top: Math.floor((element.y + scrollY) * scaleFactor) + Math.floor(headerAdjustment), + bottom: Math.ceil((element.y + element.height + scrollY) * scaleFactor) + Math.ceil(headerAdjustment), + left: Math.floor((element.x + scrollX) * scaleFactor), + right: Math.ceil((element.x + element.width + scrollX) * scaleFactor) }; const jsonObject = { @@ -280,16 +297,15 @@ export default class GenericProvider { return regionsArray; } - async updatePageShiftFactor(location, scaleFactor) { - const scrollFactors = await this.driver.executeScript({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); - if (this.currentTag.osName === 'iOS' || (this.currentTag.osName === 'OS X' && parseInt(this.currentTag.browserVersion) > 13 && this.currentTag.browserName.toLowerCase() === 'safari')) { + async updatePageShiftFactor(location, scaleFactor, scrollFactors) { + if (this.isIOS() || (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 = this.isIOS() ? 0 : (-(scrollFactors.value[0] * scaleFactor)); + if (this.isIOS() && !this.options?.fullPage) { + if (scrollFactors.value[0] !== this.initialScrollLocation.value[0] || scrollFactors.value[1] !== this.initialScrollLocation.value[1]) { this.pageXShiftFactor = (-1 * this.removeElementShiftFactor); this.pageYShiftFactor = (-1 * this.removeElementShiftFactor); } else if (location.y === 0) { @@ -303,16 +319,23 @@ 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 }; + let scrollX = 0, scrollY = 0; + const scrollFactors = await this.getScrollDetails(); + if (this.options?.fullPage) { + scrollX = scrollFactors.value[0]; + scrollY = scrollFactors.value[1]; + } + // 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, scaleFactor); + await this.updatePageShiftFactor(location, scaleFactor, scrollFactors); const coOrdinates = { - 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) + top: Math.floor((location.y + scrollY) * scaleFactor) + Math.floor(this.pageYShiftFactor), + bottom: Math.ceil((location.y + size.height + scrollY) * scaleFactor) + Math.ceil(this.pageYShiftFactor), + left: Math.floor((location.x + scrollX) * scaleFactor) + Math.floor(this.pageXShiftFactor), + right: Math.ceil((location.x + size.width + scrollX) * scaleFactor) + Math.ceil(this.pageXShiftFactor) }; const jsonObject = { @@ -325,7 +348,6 @@ export default class GenericProvider { async getSeleniumRegionsByElement(elements) { const regionsArray = []; - await this.getInitialPosition(); for (let index = 0; index < elements.length; index++) { try { const selector = `element: ${index}`; @@ -336,7 +358,10 @@ export default class GenericProvider { log.debug(e.toString()); } } - await this.scrollToInitialPosition(this.initialScrollFactor.value[0], this.initialScrollFactor.value[1]); + + if (this.isIOS()) { + await this.scrollToPosition(this.initialScrollLocation.value[0], this.initialScrollLocation.value[1]); + } return regionsArray; } diff --git a/packages/webdriver-utils/test/providers/automateProvider.test.js b/packages/webdriver-utils/test/providers/automateProvider.test.js index 70d464c29..37f0515f7 100644 --- a/packages/webdriver-utils/test/providers/automateProvider.test.js +++ b/packages/webdriver-utils/test/providers/automateProvider.test.js @@ -4,6 +4,7 @@ import AutomateProvider from '../../src/providers/automateProvider.js'; import DesktopMetaData from '../../src/metadata/desktopMetaData.js'; import Tile from '../../src/util/tile.js'; import MobileMetaData from '../../src/metadata/mobileMetaData.js'; +import Cache from '../../src/util/cache.js'; describe('AutomateProvider', () => { let superScreenshotSpy; @@ -238,43 +239,10 @@ describe('AutomateProvider', () => { }); }); - describe('getTiles', () => { - let browserstackExecutorSpy; - let executeScriptSpy; - let percyBuildInfo = { - id: '123', - url: 'https://percy.io/abc/123' - }; - const automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, 'client', 'environment', {}, percyBuildInfo); - - beforeEach(async () => { - spyOn(Driver.prototype, 'getCapabilites'); - 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') - .and.returnValue(Promise.resolve(1)); - }); - - it('should return tiles when success', async () => { - await automateProvider.createDriver(); - const res = await automateProvider.getTiles(123, 456, false); - expect(browserstackExecutorSpy).toHaveBeenCalledTimes(1); - expect(executeScriptSpy).toHaveBeenCalledTimes(1); - expect(Object.keys(res).length).toEqual(3); - expect(res.domInfoSha).toBe('abc'); - expect(res.tiles.length).toEqual(2); - expect(res.tiles[0]).toBeInstanceOf(Tile); - expect(res.tiles[1]).toBeInstanceOf(Tile); - expect(res.tiles[0].sha).toEqual('abc'); - expect(res.tiles[1].sha).toEqual('xyz'); - expect(res.tiles[0].headerHeight).toEqual(123); - expect(res.tiles[0].footerHeight).toEqual(456); - expect(res.tiles[0].navBarHeight).toEqual(0); - expect(res.tiles[0].statusBarHeight).toEqual(0); - }); - + function tilesErrorResponseCheck(automateProvider) { it('throws error when response is false', async () => { await automateProvider.createDriver(); + let browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor'); browserstackExecutorSpy.and.returnValue(Promise.resolve({ value: '{ "error": "Random Error", "success":false }' })); await expectAsync(automateProvider.getTiles(false)).toBeRejectedWithError('Failed to get screenshots from Automate.' + ' Check dashboard for error.'); @@ -284,6 +252,125 @@ describe('AutomateProvider', () => { automateProvider.driver = null; await expectAsync(automateProvider.getTiles(false)).toBeRejectedWithError('Driver is null, please initialize driver with createDriver().'); }); + } + + describe('getTiles', () => { + let percyBuildInfo = { + id: '123', + url: 'https://percy.io/abc/123' + }; + let browserstackExecutorSpy = null; + let executeScriptSpy; + + describe('fullpage', () => { + const automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }, percyBuildInfo); + + beforeEach(async () => { + Cache.reset(); + spyOn(Driver.prototype, 'getCapabilites'); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript') + .and.returnValue(Promise.resolve(1)); + }); + + it('should return tiles when success', async () => { + browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor') + .and.returnValue(Promise.resolve({ value: '{"success": true, "result": "{\\"tiles\\":[{\\"sha\\":\\"abc\\",\\"status_bar\\":0,\\"nav_bar\\":156,\\"header_height\\":0,\\"footer_height\\":156,\\"index\\":0},{\\"sha\\":\\"cde\\",\\"status_bar\\":0,\\"nav_bar\\":156,\\"header_height\\":0.0,\\"footer_height\\":156.0,\\"index\\":1}],\\"dom_sha\\":\\"def\\"}"}' })); + await automateProvider.createDriver(); + const res = await automateProvider.getTiles(false); + const expectedOutput = { + tiles: [ + new Tile({ + statusBarHeight: 0, + navBarHeight: 156, + headerHeight: 0, + footerHeight: 156, + fullscreen: false, + sha: 'abc' + }), + new Tile({ + statusBarHeight: 0, + navBarHeight: 156, + headerHeight: 0, + footerHeight: 156, + fullscreen: false, + sha: 'cde' + }) + ], + domInfoSha: 'def', + metadata: { + screenshotType: 'fullpage' + } + }; + expect(browserstackExecutorSpy).toHaveBeenCalledTimes(1); + expect(executeScriptSpy).toHaveBeenCalledTimes(1); + expect(res).toEqual(expectedOutput); + }); + + it('should return default values of header and footer if not in response', async () => { + browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor') + .and.returnValue(Promise.resolve({ value: '{"success": true, "result": "{\\"tiles\\":[{\\"sha\\":\\"abc\\",\\"index\\":0}],\\"dom_sha\\":\\"def\\"}"}' })); + await automateProvider.createDriver(); + const res = await automateProvider.getTiles(false); + const expectedOutput = { + tiles: [ + new Tile({ + statusBarHeight: 0, + navBarHeight: 0, + headerHeight: 0, + footerHeight: 0, + fullscreen: false, + sha: 'abc' + }) + ], + domInfoSha: 'def', + metadata: { + screenshotType: 'fullpage' + } + }; + expect(browserstackExecutorSpy).toHaveBeenCalledTimes(1); + expect(executeScriptSpy).toHaveBeenCalledTimes(1); + expect(res).toEqual(expectedOutput); + }); + + tilesErrorResponseCheck(automateProvider); + }); + + describe('singlepage', () => { + const automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, 'client', 'environment', {}, percyBuildInfo); + beforeEach(async () => { + Cache.reset(); + spyOn(Driver.prototype, 'getCapabilites'); + browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor') + .and.returnValue(Promise.resolve({ value: '{"success": true, "result": "{\\"tiles\\":[{\\"sha\\":\\"abc\\",\\"status_bar\\":0,\\"nav_bar\\":156,\\"header_height\\":0,\\"footer_height\\":156,\\"index\\":0}],\\"dom_sha\\":\\"def\\"}"}' })); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript') + .and.returnValue(Promise.resolve(1)); + }); + + it('should return tiles when success', async () => { + await automateProvider.createDriver(); + const res = await automateProvider.getTiles(false); + const expectedOutput = { + tiles: [ + new Tile({ + statusBarHeight: 0, + navBarHeight: 156, + headerHeight: 0, + footerHeight: 156, + fullscreen: false, + sha: 'abc' + }) + ], + domInfoSha: 'def', + metadata: { + screenshotType: 'singlepage' + } + }; + expect(browserstackExecutorSpy).toHaveBeenCalledTimes(1); + expect(executeScriptSpy).toHaveBeenCalledTimes(1); + expect(res).toEqual(expectedOutput); + }); + tilesErrorResponseCheck(automateProvider); + }); }); describe('getTag', () => { diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 8f34ce56e..7eed7b621 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -40,12 +40,12 @@ describe('GenericProvider', () => { it('creates tiles from screenshot', async () => { genericProvider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'local-poc-poa', 'staging-poc-poa', {}); genericProvider.createDriver(); - const tiles = await genericProvider.getTiles(123, 456, false); + const tiles = await genericProvider.getTiles(false); expect(tiles.tiles.length).toEqual(1); expect(tiles.tiles[0].navBarHeight).toEqual(0); expect(tiles.tiles[0].statusBarHeight).toEqual(0); - expect(tiles.tiles[0].footerHeight).toEqual(456); - expect(tiles.tiles[0].headerHeight).toEqual(123); + expect(tiles.tiles[0].footerHeight).toEqual(0); + expect(tiles.tiles[0].headerHeight).toEqual(0); expect(Object.keys(tiles)).toContain('domInfoSha'); }); @@ -175,7 +175,7 @@ describe('GenericProvider', () => { let res = await genericProvider.screenshot('mock-name', {}); expect(getTagSpy).toHaveBeenCalledTimes(1); expect(genericProvider.statusBarHeight).toEqual(0); - expect(getTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); + expect(getTilesSpy).toHaveBeenCalledOnceWith(false); expect(res).toEqual({ name: 'mock-name', tag: desktopTag, @@ -233,7 +233,7 @@ describe('GenericProvider', () => { let res = await genericProvider.screenshot('mock-name', {}); expect(iOSGetTagSpy).toHaveBeenCalledTimes(1); expect(genericProvider.statusBarHeight).toEqual(132); - expect(iOSGetTilesSpy).toHaveBeenCalledOnceWith(0, 0, false); + expect(iOSGetTilesSpy).toHaveBeenCalledOnceWith(false); expect(res).toEqual({ name: 'mock-name', tag: iosTag, @@ -266,12 +266,12 @@ describe('GenericProvider', () => { describe('updatePageShiftFactor', () => { let provider; - - describe('When iOS', () => { + let scrollFactors; + describe('When iOS singlepage screenshot', () => { beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); - spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); + scrollFactors = { value: [0, 10] }; provider.currentTag = { osName: 'iOS' }; provider.pageYShiftFactor = 0; provider.statusBarHeight = 0; @@ -279,42 +279,62 @@ describe('GenericProvider', () => { describe('when element is visible in viewport', () => { beforeEach(() => { - provider.initialScrollFactor = { value: [0, 10] }; + provider.initialScrollLocation = { value: [0, 10] }; }); it('should update pageYShiftFactor for iOS when location.y is 0', async () => { - await provider.updatePageShiftFactor({ y: 0 }, 2); + await provider.updatePageShiftFactor({ y: 0 }, 2, scrollFactors); 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 }, 2); + await provider.updatePageShiftFactor({ y: 5 }, 2, scrollFactors); expect(provider.pageYShiftFactor).toBe(0); }); }); describe('when element is not visible in viewport and iOS scrolls automatically', () => { beforeEach(() => { - provider.initialScrollFactor = { value: [0, 30] }; + provider.initialScrollLocation = { value: [0, 30] }; }); it('should update pageYShiftFactor to negative value even if location.y is 0', async () => { - await provider.updatePageShiftFactor({ y: 0 }, 2); + await provider.updatePageShiftFactor({ y: 0 }, 2, scrollFactors); 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 }, 2); + await provider.updatePageShiftFactor({ y: 5 }, 2, scrollFactors); expect(provider.pageYShiftFactor).toBe(-50000); }); }); }); - describe('When OS X', () => { + describe('When iOS fullpage screenshot', () => { + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }); + await provider.createDriver(); + scrollFactors = { value: [0, 10] }; + provider.currentTag = { osName: 'iOS' }; + provider.pageYShiftFactor = 0; + }); + + describe('when element is present in DOM', () => { + beforeEach(() => { + provider.statusBarHeight = 10; + }); + it('should update pageYShiftFactor for iOS to statusBarHeight', async () => { + await provider.updatePageShiftFactor({ y: 0 }, 2, scrollFactors); + expect(provider.pageYShiftFactor).toBe(10); + }); + }); + }); + + describe('When OS X singlepage', () => { beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'OS X' }, {}); await provider.createDriver(); - spyOn(Driver.prototype, 'executeScript').and.returnValue({ value: [0, 10] }); + scrollFactors = { value: [0, 10] }; provider.currentTag = { osName: 'OS X' }; provider.pageYShiftFactor = 0; provider.statusBarHeight = 0; @@ -323,13 +343,13 @@ describe('GenericProvider', () => { describe('When Safari browserVersion > 13', () => { describe('when element is visible in viewport', () => { beforeEach(() => { - provider.initialScrollFactor = { value: [0, 10] }; + provider.initialScrollLocation = { 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); + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); expect(provider.pageYShiftFactor).toBe(0); }); }); @@ -338,20 +358,61 @@ describe('GenericProvider', () => { describe('When Safari browserVersion <= 13', () => { describe('when element is visible in viewport', () => { beforeEach(() => { - provider.initialScrollFactor = { value: [0, 10] }; + provider.initialScrollLocation = { 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); + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); expect(provider.pageYShiftFactor).toBe(-10); }); }); }); }); - describe('When Other', () => { + describe('When OS X fullpage screenshot', () => { + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'OS X' }, {}, 'client', 'environment', { fullPage: true }); + await provider.createDriver(); + scrollFactors = { value: [0, 10] }; + provider.currentTag = { osName: 'OS X' }; + provider.pageYShiftFactor = 0; + }); + + describe('When Safari browserVersion > 13', () => { + describe('when element is present in DOM', () => { + beforeEach(() => { + provider.currentTag.browserName = 'safari'; + provider.currentTag.browserVersion = 15; + provider.statusBarHeight = 0; + }); + + it('should update pageYShiftFactor for OS X to statusBarHeight', async () => { + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); + expect(provider.pageYShiftFactor).toBe(0); + }); + }); + }); + + describe('When Safari browserVersion <= 13', () => { + describe('when element is present in DOM', () => { + beforeEach(() => { + provider.currentTag.browserName = 'safari'; + provider.currentTag.browserVersion = 13; + provider.statusBarHeight = 20; + }); + + it('should update pageYShiftFactor for OS X platforms accordingly if scrolled', async () => { + scrollFactors = { value: [0, 10] }; + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); + expect(provider.pageYShiftFactor).toBe(10); + }); + }); + }); + }); + + describe('When Other singlepage', () => { beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); @@ -360,14 +421,30 @@ 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 }, 1); + scrollFactors = { value: [0, 0] }; + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); 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 }, 1); + scrollFactors = { value: [0, 10] }; + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); + expect(provider.pageYShiftFactor).toBe(-10); + }); + }); + + describe('When Other fullpage', () => { + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }); + await provider.createDriver(); + provider.currentTag = { osName: 'Android' }; + provider.pageYShiftFactor = 0; + provider.statusBarHeight = 0; + }); + + it('should update pageYShiftFactor for non-iOS platforms accordingly if scrolled', async () => { + scrollFactors = { value: [0, 10] }; + await provider.updatePageShiftFactor({ y: 0 }, 1, scrollFactors); expect(provider.pageYShiftFactor).toBe(-10); }); }); @@ -376,8 +453,25 @@ describe('GenericProvider', () => { describe('getRegionObject', () => { let provider; let mockLocation = { x: 10, y: 20, width: 100, height: 200 }; + let scrollFactors = { value: [0, 0] }; - describe('When on Tile 0', () => { + function expectRegionObject(scrollX, scrollY) { + it('should return a JSON object with the correct selector and coordinates for tile', async () => { + // 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 + scrollY + provider.pageYShiftFactor, + bottom: mockLocation.y + mockLocation.height + scrollY + provider.pageYShiftFactor, + left: mockLocation.x + scrollX + provider.pageXShiftFactor, + right: mockLocation.x + mockLocation.width + scrollX + provider.pageXShiftFactor + }); + }); + } + describe('When singlepage screenshot', () => { beforeEach(async () => { // mock metadata provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); @@ -385,62 +479,54 @@ describe('GenericProvider', () => { await provider.createDriver(); spyOn(DesktopMetaData.prototype, 'devicePixelRatio') .and.returnValue(1); - spyOn(Driver.prototype, 'executeScript') - .and.returnValue({ value: [0, 0] }); + spyOn(GenericProvider.prototype, 'getScrollDetails') + .and.returnValue(scrollFactors); 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'); + describe('When on Tile 0', () => { + expectRegionObject(0, 0); + }); - // 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 on Tile 1', () => { + beforeEach(async () => { + // mock metadata + provider.currentTag = { osName: 'iOS' }; + provider.pageYShiftFactor = -10; + provider.initialScrollLocation = scrollFactors; }); + expectRegionObject(0, 0); }); }); - describe('When on Tile 1', () => { + describe('When fullpage screenshot', () => { beforeEach(async () => { // mock metadata - provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); - provider.currentTag = { osName: 'iOS' }; + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }); + provider.currentTag = { osName: 'Windows' }; 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; }); - afterEach(() => { - provider.pageYShiftFactor = 0; - provider.currentTag = null; + describe('When no scroll', () => { + expectRegionObject(0, 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 + describe('When there is a scroll', () => { + let scrollX = 10; + let scrollY = 20; + beforeEach(async () => { + // mock metadata + provider.currentTag = { osName: 'iOS' }; + spyOn(GenericProvider.prototype, 'getScrollDetails') + .and.returnValue({ value: [scrollX, scrollY] }); + provider.pageYShiftFactor = -10; }); + expectRegionObject(scrollX, scrollY); }); }); }); @@ -458,43 +544,58 @@ describe('GenericProvider', () => { provider.statusBarHeight = 0; }); - describe('When not an iOS', () => { + function expectRegionObjectFromBoundingBox(scrollX, scrollY) { + // Call function with mock data 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 + top: mockLocation.y + scrollY + provider.statusBarHeight, + bottom: mockLocation.y + mockLocation.height + scrollY + provider.statusBarHeight, + left: mockLocation.x + scrollX, + right: mockLocation.x + mockLocation.width + scrollX }); }); - }); + } - describe('When iOS', () => { - beforeEach(() => { - provider.currentTag = { osName: 'iOS' }; - provider.statusBarHeight = 132; + describe('When singlepage screenshot', () => { + describe('When not an iOS', () => { + expectRegionObjectFromBoundingBox(0, 0); + }); + + describe('When iOS', () => { + beforeEach(() => { + provider.currentTag = { osName: 'iOS' }; + provider.statusBarHeight = 132; + }); + + expectRegionObjectFromBoundingBox(0, 0); }); - it('should return a JSON object with the correct selector and coordinates with added statusBarHeight', async () => { + }); + + describe('When fullpage screenshot', () => { + let scrollX = 10; + let scrollY = 20; + beforeEach(async () => { + // mock metadata + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }); + provider.currentTag = { osName: 'Windows' }; await provider.createDriver(); + provider.initialScrollLocation = { value: [scrollX, scrollY] }; + }); - // Call function with mock data - const selector = 'mock-selector'; - const result = await provider.getRegionObjectFromBoundingBox(selector, mockLocation); + describe('When not an iOS', () => { + expectRegionObjectFromBoundingBox(scrollX, scrollY); + }); - // 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('When iOS', () => { + beforeEach(() => { + provider.currentTag = { osName: 'iOS' }; + provider.statusBarHeight = 132; }); + expectRegionObjectFromBoundingBox(scrollX, scrollY); }); }); }); @@ -566,105 +667,79 @@ describe('GenericProvider', () => { }); }); - describe('getInitialPosition', () => { + describe('scrollToPosition', () => { let provider; + let executeScriptSpy; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); provider.currentTag = { osName: 'Windows' }; 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] }); - }); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); }); - describe('when IOS', () => { - let executeScriptSpy; - beforeEach(() => { - provider.currentTag = { osName: 'iOS' }; - executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); - }); - - afterEach(() => { - provider.currentTag = 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), parseInt(window.scrollY)];', args: [] }); - expect(provider.initialScrollFactor).toEqual({ value: [0, 200] }); - }); + it('should scroll to correct position', async () => { + await provider.scrollToPosition(10, 20); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'window.scrollTo(10, 20)', args: [] }); }); }); - describe('scrollToInitialPosition', () => { + describe('isIOS', () => { let provider; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); provider.currentTag = { osName: 'Windows' }; - 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.currentTag = { osName: 'iOS' }; - provider.initialScrollFactor = { value: [0, 50] }; - executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); - }); + it('when not iOS returns false', async () => { + let result = provider.isIOS(); + expect(result).toEqual(false); + }); - afterEach(() => { - provider.currentTag = 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: [] }); - }); + it('when iOS returns true', async () => { + provider.currentTag = { osName: 'iOS' }; + let result = provider.isIOS(); + expect(result).toEqual(true); }); }); describe('getSeleniumRegionsByElement', () => { let getRegionObjectSpy; - let getInitialPositionSpy; - let scrollToInitialPositionSpy; + let scrollToPositionSpy; let provider; + const elements = ['mockElement_1', 'mockElement_2', 'mockElement_3']; 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'); + scrollToPositionSpy = spyOn(GenericProvider.prototype, 'scrollToPosition'); }); 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([{}, {}, {}]); }); it('should ignore when error', async () => { getRegionObjectSpy.and.rejectWith(new Error('Element not found')); - const elements = ['mockElement_1', 'mockElement_2', 'mockElement_3']; const elementsArray = await provider.getSeleniumRegionsByElement(elements); expect(elementsArray).toEqual([]); }); + + it('should not scroll back to initial position for non iOS', async () => { + await provider.getSeleniumRegionsByElement(elements); + expect(scrollToPositionSpy).not.toHaveBeenCalled(); + }); + + it('should scroll back to initial position for iOS', async () => { + provider.currentTag = { osName: 'iOS' }; + provider.initialScrollLocation = { value: [10, 20] }; + await provider.getSeleniumRegionsByElement(elements); + expect(scrollToPositionSpy).toHaveBeenCalledTimes(1); + }); }); describe('getSeleniumRegionsByLocation', () => { @@ -762,11 +837,13 @@ describe('GenericProvider', () => { describe('doTransformations', () => { let provider; let executeScriptSpy; + let getInitialScrollLocationSpy; beforeEach(async () => { provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); await provider.createDriver(); executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + getInitialScrollLocationSpy = spyOn(GenericProvider.prototype, 'getInitialScrollLocation'); }); it('should do transfomation', async () => { @@ -789,6 +866,25 @@ describe('GenericProvider', () => { await provider.doTransformations(); expect(executeScriptSpy).toHaveBeenCalledWith({ script: jsScript, args: [] }); }); + + it('should not get initial scroll position for singlepage for non ios', async () => { + await provider.doTransformations(); + expect(getInitialScrollLocationSpy).not.toHaveBeenCalled(); + }); + + it('should get initial scroll position for singlepage for ios', async () => { + provider.currentTag = { osName: 'iOS' }; + await provider.createDriver(); + await provider.doTransformations(); + expect(getInitialScrollLocationSpy).toHaveBeenCalled(); + }); + + it('should get initial scroll position for singlepage', async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}, 'client', 'environment', { fullPage: true }); + await provider.createDriver(); + await provider.doTransformations(); + expect(getInitialScrollLocationSpy).toHaveBeenCalled(); + }); }); describe('undoTransformations', () => { @@ -811,4 +907,42 @@ describe('GenericProvider', () => { expect(executeScriptSpy).toHaveBeenCalledWith({ script: jsScript, args: [] }); }); }); + + describe('getScrollDetails', () => { + let provider; + let executeScriptSpy; + + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + }); + + it('should return scroll params', async () => { + await provider.getScrollDetails(); + expect(executeScriptSpy).toHaveBeenCalledWith({ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];', args: [] }); + }); + }); + + describe('getInitialScrollLocation', () => { + let provider; + let getScrollDetailsSpy; + + beforeEach(async () => { + provider = new GenericProvider('123', 'http:executorUrl', { platform: 'win' }, {}); + await provider.createDriver(); + getScrollDetailsSpy = spyOn(GenericProvider.prototype, 'getScrollDetails'); + provider.initialScrollLocation = { value: [1, 1] }; + }); + + it('do not get scroll details if already present', async () => { + provider.getInitialScrollLocation(); + expect(getScrollDetailsSpy).not.toHaveBeenCalled(); + }); + it('gets scroll details if not present', async () => { + provider.initialScrollLocation = null; + provider.getInitialScrollLocation(); + expect(getScrollDetailsSpy).toHaveBeenCalled(); + }); + }); });