From 3de2a7cd017fb370c3290ecda07534c894d93e26 Mon Sep 17 00:00:00 2001 From: Pradum Kumar Date: Tue, 3 Sep 2024 15:09:08 +0530 Subject: [PATCH] adding support for scrollSpeed and androidScrollAreaPercentage (#346) --- README.md | 3 + index.js | 10 +- percy/providers/appAutomateProvider.js | 164 +++++++++++------- .../providers/appAutomateProvider.test.mjs | 103 ++++++----- 4 files changed, 174 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 160f822..582c89e 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,9 @@ await percyScreenshotFlutter(driver, name[, { - `bottom` (int): Bottom coordinate of the consider region. - `left` (int): Left coordinate of the consider region. - `right` (int): Right coordinate of the consider region. + - `androidScrollAreaPercentage` - Percentage Area to scroll for android devices. (should be between 0 and 100) + - `scrollSpeed` - Scroll speed in pixel/second. (Should be between 0 and 5000) + ### Creating Percy on automate build Note: Automate Percy Token starts with `auto` keyword. The command can be triggered using `exec` keyword. ```sh-session diff --git a/index.js b/index.js index 223b291..45b6946 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,9 @@ module.exports = async function percyScreenshot(driver, name, options = {}) { bottomScrollviewOffset, scrollableId, sync, - testCase + testCase, + androidScrollAreaPercentage, + scrollSpeed } = options; // allow working with or without standalone mode for wdio if (!driver || typeof driver === 'string') { @@ -58,6 +60,8 @@ module.exports = async function percyScreenshot(driver, name, options = {}) { scrollableId = name.scrollableId; sync = name.sync; testCase = name.testCase; + androidScrollAreaPercentage = name.androidScrollAreaPercentage; + scrollSpeed = name.scrollSpeed; options = name; } try { @@ -111,7 +115,9 @@ module.exports = async function percyScreenshot(driver, name, options = {}) { scrollableId, sync, testCase, - thTestCaseExecutionId + thTestCaseExecutionId, + androidScrollAreaPercentage, + scrollSpeed }); log.debug(`[${name}] -> end`); return response?.body?.data; diff --git a/percy/providers/appAutomateProvider.js b/percy/providers/appAutomateProvider.js index 0423f53..21ae9d7 100644 --- a/percy/providers/appAutomateProvider.js +++ b/percy/providers/appAutomateProvider.js @@ -11,33 +11,40 @@ class AppAutomateProvider extends GenericProvider { } static supports(driver) { - return driver.remoteHostname.includes(process.env.AA_DOMAIN || 'browserstack'); + return driver.remoteHostname.includes( + process.env.AA_DOMAIN || 'browserstack' + ); } - async screenshot(name, { - fullscreen, - deviceName, - orientation, - statusBarHeight, - navigationBarHeight, - fullPage, - screenLengths, - ignoreRegionXpaths, - ignoreRegionAccessibilityIds, - ignoreRegionAppiumElements, - customIgnoreRegions, - considerRegionXpaths, - considerRegionAccessibilityIds, - considerRegionAppiumElements, - customConsiderRegions, - scrollableXpath, - topScrollviewOffset, - bottomScrollviewOffset, - scrollableId, - sync, - testCase, - thTestCaseExecutionId - } = {}) { + async screenshot( + name, + { + fullscreen, + deviceName, + orientation, + statusBarHeight, + navigationBarHeight, + fullPage, + screenLengths, + ignoreRegionXpaths, + ignoreRegionAccessibilityIds, + ignoreRegionAppiumElements, + customIgnoreRegions, + considerRegionXpaths, + considerRegionAccessibilityIds, + considerRegionAppiumElements, + customConsiderRegions, + scrollableXpath, + topScrollviewOffset, + bottomScrollviewOffset, + scrollableId, + sync, + testCase, + thTestCaseExecutionId, + androidScrollAreaPercentage, + scrollSpeed + } = {} + ) { let response = null; let error; sync = sync || null; @@ -67,13 +74,20 @@ class AppAutomateProvider extends GenericProvider { scrollableId, sync, testCase, - thTestCaseExecutionId + thTestCaseExecutionId, + androidScrollAreaPercentage, + scrollSpeed }); } catch (e) { error = e; throw e; } finally { - await this.percyScreenshotEnd(name, response?.body?.link, sync, `${error}`); + await this.percyScreenshotEnd( + name, + response?.body?.link, + sync, + `${error}` + ); } return response; } @@ -97,7 +111,12 @@ class AppAutomateProvider extends GenericProvider { }); } - async percyScreenshotEnd(name, percyScreenshotUrl, sync, statusMessage = null) { + async percyScreenshotEnd( + name, + percyScreenshotUrl, + sync, + statusMessage = null + ) { return await TimeIt.run('percyScreenshotEnd', async () => { try { await this.browserstackExecutor('percyScreenshot', { @@ -115,11 +134,23 @@ class AppAutomateProvider extends GenericProvider { } // Override this for AA specific optimizations - async getTiles(fullscreen, fullPage, screenLengths, scrollableXpath, topScrollviewOffset, bottomScrollviewOffset, scrollableId) { + async getTiles( + fullscreen, + fullPage, + screenLengths, + scrollableXpath, + topScrollviewOffset, + bottomScrollviewOffset, + scrollableId, + androidScrollAreaPercentage, + scrollSpeed + ) { // Override AA optimizations if (this.isDisableRemoteUpload()) { if (fullPage === true) { - log.warn('Full page screenshots are only supported when "PERCY_DISABLE_REMOTE_UPLOADS" is not set'); + log.warn( + 'Full page screenshots are only supported when "PERCY_DISABLE_REMOTE_UPLOADS" is not set' + ); } return await super.getTiles(fullscreen, fullPage, screenLengths); } @@ -133,43 +164,52 @@ class AppAutomateProvider extends GenericProvider { projectId = 'percy-dev'; } // Take screenshots via browserstack executor - const response = await TimeIt.run('percyScreenshot:screenshot', async () => { - return await this.browserstackExecutor('percyScreenshot', { - state: 'screenshot', - percyBuildId: utils.percy?.build?.id, - screenshotType, - projectId, - scaleFactor: await this.metadata.scaleFactor(), - options: { - numOfTiles: screenLengths || 4, - deviceHeight: (await this.metadata.screenSize()).height, - scollableXpath: scrollableXpath || null, - topScrollviewOffset: topScrollviewOffset || 0, - bottomScrollviewOffset: bottomScrollviewOffset || 0, - scrollableId: scrollableId || null, - FORCE_FULL_PAGE: process.env.FORCE_FULL_PAGE === 'true' - } - }); - }); + const response = await TimeIt.run( + 'percyScreenshot:screenshot', + async () => { + return await this.browserstackExecutor('percyScreenshot', { + state: 'screenshot', + percyBuildId: utils.percy?.build?.id, + screenshotType, + projectId, + scaleFactor: await this.metadata.scaleFactor(), + options: { + numOfTiles: screenLengths || 4, + deviceHeight: (await this.metadata.screenSize()).height, + scollableXpath: scrollableXpath || null, + topScrollviewOffset: topScrollviewOffset || 0, + bottomScrollviewOffset: bottomScrollviewOffset || 0, + scrollableId: scrollableId || null, + FORCE_FULL_PAGE: process.env.FORCE_FULL_PAGE === 'true', + androidScrollAreaPercentage: androidScrollAreaPercentage || null, + scrollSpeed: scrollSpeed || null + } + }); + } + ); if (!response.success) { - throw new Error('Failed to get screenshots from App Automate.' + - ' Check dashboard for error.'); + throw new Error( + 'Failed to get screenshots from App Automate.' + + ' Check dashboard for error.' + ); } const tiles = []; const statBarHeight = await this.metadata.statusBarHeight(); const navBarHeight = await this.metadata.navigationBarHeight(); - JSON.parse(response.result).forEach(tileData => { - tiles.push(new Tile({ - statusBarHeight: statBarHeight, - navBarHeight, - fullscreen, - headerHeight: tileData.header_height, - footerHeight: tileData.footer_height, - sha: tileData.sha.split('-')[0] // drop build id - })); + JSON.parse(response.result).forEach((tileData) => { + tiles.push( + new Tile({ + statusBarHeight: statBarHeight, + navBarHeight, + fullscreen, + headerHeight: tileData.header_height, + footerHeight: tileData.footer_height, + sha: tileData.sha.split('-')[0] // drop build id + }) + ); }); return tiles; @@ -177,7 +217,11 @@ class AppAutomateProvider extends GenericProvider { async browserstackExecutor(action, args) { let options = args ? { action, arguments: args } : { action }; - return JSON.parse(await this.driver.execute(`browserstack_executor: ${JSON.stringify(options)}`)); + return JSON.parse( + await this.driver.execute( + `browserstack_executor: ${JSON.stringify(options)}` + ) + ); } setDebugUrl(result) { diff --git a/test/percy/providers/appAutomateProvider.test.mjs b/test/percy/providers/appAutomateProvider.test.mjs index 84f216a..f29f6f7 100644 --- a/test/percy/providers/appAutomateProvider.test.mjs +++ b/test/percy/providers/appAutomateProvider.test.mjs @@ -121,16 +121,16 @@ describe('AppAutomateProvider', () => { scrollableId: null, FORCE_FULL_PAGE: false } - } - describe ('when taking singlepage', () => { - describe ('when env isDisableRemoteUpload is true', () => { + }; + + describe('when taking singlepage', () => { + describe('when env isDisableRemoteUpload is true', () => { it('takes a local appium screenshot', async () => { const appAutomate = new AppAutomateProvider(driver); spyOn(AppAutomateProvider.prototype, 'isDisableRemoteUpload') - .and.returnValue('true'); + .and.returnValue('true'); let superGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles'); - superGetTilesSpy.and.resolveTo([]) - + superGetTilesSpy.and.resolveTo([]); await appAutomate.getTiles(true, false, null, null, null); expect(superGetTilesSpy).toHaveBeenCalledWith(true, false, null); }); @@ -140,38 +140,52 @@ describe('AppAutomateProvider', () => { it('takes screenshot with remote executor', async () => { const appAutomate = new AppAutomateProvider(driver); let superGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles'); - let browserstack_executorSpy = spyOn(AppAutomateProvider.prototype, 'browserstackExecutor'); - superGetTilesSpy.and.resolveTo([]) + let browserstackExecutorSpy = spyOn( + AppAutomateProvider.prototype, + 'browserstackExecutor' + ); + superGetTilesSpy.and.resolveTo([]); let response = { success: true, - result: JSON.stringify([{header_height: 100, footer_height: 200, sha: "abc"}]) + result: JSON.stringify([ + { header_height: 100, footer_height: 200, sha: 'abc' } + ]) }; - browserstack_executorSpy.and.resolveTo(response); + browserstackExecutorSpy.and.resolveTo(response); var screenSize = { - height: 2000, + height: 2000 + }; + args.options.deviceHeight = screenSize.height; + args.options.topScrollviewOffset = 0; + args.options.bottomScrollviewOffset = 0; + args.options.androidScrollAreaPercentage = null; + args.options.scrollSpeed = null; + appAutomate.metadata = { + statusBarHeight: () => 100, + navigationBarHeight: () => 200, + scaleFactor: () => 1, + screenSize: () => screenSize }; - args['options']['deviceHeight'] = screenSize['height']; - args['options']['topScrollviewOffset'] = 0; - args['options']['bottomScrollviewOffset'] = 0; - appAutomate.metadata = { statusBarHeight: () => 100, navigationBarHeight: () => 200, scaleFactor: () => 1, screenSize: () => screenSize }; - let tiles = await appAutomate.getTiles(true, false, null, null, null); expect(tiles[0].statusBarHeight).toEqual(100); expect(tiles[0].navBarHeight).toEqual(200); - expect(browserstack_executorSpy).toHaveBeenCalledWith('percyScreenshot', args); + expect(browserstackExecutorSpy).toHaveBeenCalledWith( + 'percyScreenshot', + args + ); }); }); }); - describe ('when taking fullpage', () => { - describe ('when env isDisableRemoteUpload is true', () => { + describe('when taking fullpage', () => { + describe('when env isDisableRemoteUpload is true', () => { it('takes a local appium screenshot', async () => { const appAutomate = new AppAutomateProvider(driver); spyOn(AppAutomateProvider.prototype, 'isDisableRemoteUpload') - .and.returnValue('true'); + .and.returnValue('true'); let superGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles'); - superGetTilesSpy.and.resolveTo([]) - + superGetTilesSpy.and.resolveTo([]); + await appAutomate.getTiles(true, true, null, null, null); expect(superGetTilesSpy).toHaveBeenCalledWith(true, true, null); }); @@ -181,22 +195,22 @@ describe('AppAutomateProvider', () => { it('takes screenshot with remote executor', async () => { const appAutomate = new AppAutomateProvider(driver); let superGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles'); - let browserstack_executorSpy = spyOn(AppAutomateProvider.prototype, 'browserstackExecutor'); - superGetTilesSpy.and.resolveTo([]) + let browserstackExecutorSpy = spyOn(AppAutomateProvider.prototype, 'browserstackExecutor'); + superGetTilesSpy.and.resolveTo([]); let response = { success: true, - result: JSON.stringify([{header_height: 100, footer_height: 200, sha: "abc"}]) + result: JSON.stringify([{ header_height: 100, footer_height: 200, sha: 'abc' }]) }; - browserstack_executorSpy.and.resolveTo(response); + browserstackExecutorSpy.and.resolveTo(response); var screenSize = { - height: 2000, + height: 2000 }; - args['options']['deviceHeight'] = screenSize['height']; - args['screenshotType'] = 'fullpage'; + args.options.deviceHeight = screenSize.height; + args.screenshotType = 'fullpage'; appAutomate.metadata = { statusBarHeight: () => 100, navigationBarHeight: () => 200, scaleFactor: () => 1, screenSize: () => screenSize }; - + await appAutomate.getTiles(true, true, null, null, null); - expect(browserstack_executorSpy).toHaveBeenCalledWith('percyScreenshot', args); + expect(browserstackExecutorSpy).toHaveBeenCalledWith('percyScreenshot', args); }); }); @@ -204,27 +218,28 @@ describe('AppAutomateProvider', () => { it('takes screenshot with remote executor with "percy-dev" as projectId', async () => { const appAutomate = new AppAutomateProvider(driver); spyOn(AppAutomateProvider.prototype, 'isPercyDev') - .and.returnValue('true'); + .and.returnValue('true'); let superGetTilesSpy = spyOn(GenericProvider.prototype, 'getTiles'); - let browserstack_executorSpy = spyOn(AppAutomateProvider.prototype, 'browserstackExecutor'); - superGetTilesSpy.and.resolveTo([]) + let browserstackExecutorSpy = spyOn(AppAutomateProvider.prototype, 'browserstackExecutor'); + superGetTilesSpy.and.resolveTo([]); let response = { success: true, - result: JSON.stringify([{header_height: 100, footer_height: 200, sha: "abc"}]) + result: JSON.stringify([{ header_height: 100, footer_height: 200, sha: 'abc' }]) }; - browserstack_executorSpy.and.resolveTo(response); + browserstackExecutorSpy.and.resolveTo(response); var screenSize = { - height: 2000, + height: 2000 }; - args['projectId'] = 'percy-dev'; - args['options']['deviceHeight'] = screenSize['height']; - args['options']['topScrollviewOffset'] = 0; - args['options']['bottomScrollviewOffset'] = 0; - args['screenshotType'] = 'fullpage'; + args.projectId = 'percy-dev'; + args.options.deviceHeight = screenSize.height; + args.options.topScrollviewOffset = 0; + args.options.bottomScrollviewOffset = 0; + args.options.androidScrollAreaPercentage = null; + args.options.scrollSpeed = null; + args.screenshotType = 'fullpage'; appAutomate.metadata = { statusBarHeight: () => 100, navigationBarHeight: () => 200, scaleFactor: () => 1, screenSize: () => screenSize }; - await appAutomate.getTiles(true, true, null, null, null); - expect(browserstack_executorSpy).toHaveBeenCalledWith('percyScreenshot', args); + expect(browserstackExecutorSpy).toHaveBeenCalledWith('percyScreenshot', args); }); }); });