From 0e77b6ea856c391e42f77a5d0520914d2e10bd22 Mon Sep 17 00:00:00 2001 From: amit3200 Date: Mon, 24 Jul 2023 03:05:40 +0530 Subject: [PATCH] Adding support for legacy protocol --- packages/webdriver-utils/src/driver.js | 15 +- packages/webdriver-utils/src/index.js | 6 +- .../src/metadata/desktopMetaData.js | 32 +++-- .../src/metadata/metaDataResolver.js | 4 +- .../src/metadata/mobileMetaData.js | 32 +++-- .../src/metadata/normalizeData.js | 34 +++++ .../src/providers/automateProvider.js | 64 +++++++-- .../src/providers/genericProvider.js | 15 +- packages/webdriver-utils/src/util/cache.js | 2 + packages/webdriver-utils/test/driver.test.js | 29 +++- .../test/metadata/desktopMetaData.test.js | 6 + .../test/metadata/mobileMetaData.test.js | 10 +- .../test/metadata/normalizeData.test.js | 84 ++++++++++++ .../test/providers/automateProvider.test.js | 128 ++++++++++++++++-- .../test/providers/genericProvider.test.js | 13 +- 15 files changed, 405 insertions(+), 69 deletions(-) create mode 100644 packages/webdriver-utils/src/metadata/normalizeData.js create mode 100644 packages/webdriver-utils/test/metadata/normalizeData.test.js diff --git a/packages/webdriver-utils/src/driver.js b/packages/webdriver-utils/src/driver.js index 0deff293c..9c60ec9c6 100644 --- a/packages/webdriver-utils/src/driver.js +++ b/packages/webdriver-utils/src/driver.js @@ -1,18 +1,25 @@ import utils from '@percy/sdk-utils'; import Cache from './util/cache.js'; const { request } = utils; +const log = utils.logger('webdriver-utils:driver'); export default class Driver { - constructor(sessionId, executorUrl) { + constructor(sessionId, executorUrl, passedCapabilities) { this.sessionId = sessionId; this.executorUrl = executorUrl.includes('@') ? `https://${executorUrl.split('@')[1]}` : executorUrl; + this.passedCapabilities = passedCapabilities; } async getCapabilites() { return await Cache.withCache(Cache.caps, this.sessionId, async () => { - const baseUrl = `${this.executorUrl}/session/${this.sessionId}`; - const caps = JSON.parse((await request(baseUrl)).body); - return caps.value; + try { + const baseUrl = `${this.executorUrl}/session/${this.sessionId}`; + const caps = JSON.parse((await request(baseUrl)).body); + return caps.value; + } catch (err) { + log.warn(`Falling back to legacy protocol, Error: ${err.message}`); + return this.passedCapabilities; + } }); } diff --git a/packages/webdriver-utils/src/index.js b/packages/webdriver-utils/src/index.js index c72d0fec7..577eb703b 100644 --- a/packages/webdriver-utils/src/index.js +++ b/packages/webdriver-utils/src/index.js @@ -34,11 +34,11 @@ export default class WebdriverUtils { async automateScreenshot() { try { - this.log.info('Starting automate screenshot ...'); + this.log.info(`[${this.snapshotName}] : Starting automate screenshot ...`); const automate = ProviderResolver.resolve(this.sessionId, this.commandExecutorUrl, this.capabilities, this.sessionCapabilites, this.clientInfo, this.environmentInfo, this.options, this.buildInfo); - this.log.debug('Resolved provider ...'); + this.log.debug(`[${this.snapshotName}] : Resolved provider ...`); await automate.createDriver(); - this.log.debug('Created driver ...'); + this.log.debug(`[${this.snapshotName}] : Created driver ...`); return await automate.screenshot(this.snapshotName, this.options); } catch (e) { this.log.error(`[${this.snapshotName}] : Error - ${e.message}`); diff --git a/packages/webdriver-utils/src/metadata/desktopMetaData.js b/packages/webdriver-utils/src/metadata/desktopMetaData.js index c044b8bbb..ef0fd1653 100644 --- a/packages/webdriver-utils/src/metadata/desktopMetaData.js +++ b/packages/webdriver-utils/src/metadata/desktopMetaData.js @@ -1,28 +1,34 @@ +import Cache from '../util/cache.js'; + export default class DesktopMetaData { constructor(driver, opts) { this.driver = driver; this.capabilities = opts; } + device() { + return false; + } + browserName() { - return this.capabilities.browserName.toLowerCase(); + return this.capabilities?.browserName?.toLowerCase(); } browserVersion() { - return this.capabilities.browserVersion.split('.')[0]; + return this.capabilities?.browserVersion?.split('.')[0]; } osName() { - let osName = this.capabilities.os; - if (osName) return osName.toLowerCase(); + let osName = this.capabilities?.os; + if (osName) return osName?.toLowerCase(); - osName = this.capabilities.platform; + osName = this.capabilities?.platform; return osName; } // showing major version osVersion() { - return this.capabilities.osVersion.toLowerCase(); + return this.capabilities?.osVersion?.toLowerCase(); } // combination of browserName + browserVersion + osVersion + osName @@ -42,13 +48,17 @@ export default class DesktopMetaData { } async screenResolution() { - const data = await this.driver.executeScript({ script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];', args: [] }); - const screenInfo = data.value; - return `${screenInfo[0]} x ${screenInfo[1]}`; + 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 screenInfo = data.value; + return `${screenInfo[0]} x ${screenInfo[1]}`; + }); } async devicePixelRatio() { - const devicePixelRatio = await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }); - return devicePixelRatio.value; + return await Cache.withCache(Cache.dpr, this.driver.sessionId, async () => { + const devicePixelRatio = await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }); + return devicePixelRatio.value; + }); } } diff --git a/packages/webdriver-utils/src/metadata/metaDataResolver.js b/packages/webdriver-utils/src/metadata/metaDataResolver.js index c7f79177b..fd3b51824 100644 --- a/packages/webdriver-utils/src/metadata/metaDataResolver.js +++ b/packages/webdriver-utils/src/metadata/metaDataResolver.js @@ -6,7 +6,9 @@ export default class MetaDataResolver { if (!driver) throw new Error('Please pass a Driver object'); const platform = opts.platformName || opts.platform; - if (['ios', 'android'].includes(platform.toLowerCase())) { + if (['ios', 'android'].includes(platform.toLowerCase()) || + ['ios', 'android'].includes(capabilities?.platformName?.toLowerCase()) || + ['ipad', 'iphone'].includes(capabilities?.device?.toString()?.toLowerCase())) { return new MobileMetaData(driver, capabilities); } else { return new DesktopMetaData(driver, capabilities); diff --git a/packages/webdriver-utils/src/metadata/mobileMetaData.js b/packages/webdriver-utils/src/metadata/mobileMetaData.js index e00b6e4ea..c2620c960 100644 --- a/packages/webdriver-utils/src/metadata/mobileMetaData.js +++ b/packages/webdriver-utils/src/metadata/mobileMetaData.js @@ -1,11 +1,17 @@ +import Cache from '../util/cache.js'; +// Todo: Implement a base metadata for the common functions. export default class MobileMetaData { constructor(driver, opts) { this.driver = driver; this.capabilities = opts; } + device() { + return true; + } + browserName() { - return this.capabilities.browserName.toLowerCase(); + return this.capabilities?.browserName?.toLowerCase(); } browserVersion() { @@ -13,11 +19,11 @@ export default class MobileMetaData { if (bsVersion?.length > 0) { return bsVersion[0]; } - return this.capabilities.version.split('.')[0]; + return this.capabilities?.version?.split('.')[0]; } osName() { - let osName = this.capabilities.os.toLowerCase(); + let osName = this.capabilities?.os?.toLowerCase(); if (osName === 'mac' && this.browserName() === 'iphone') { osName = 'ios'; } @@ -25,15 +31,15 @@ export default class MobileMetaData { } osVersion() { - return this.capabilities.osVersion.split('.')[0]; + return this.capabilities?.osVersion?.split('.')[0]; } deviceName() { - return this.capabilities.deviceName.split('-')[0]; + return this.capabilities?.deviceName?.split('-')[0]; } orientation() { - return this.capabilities.orientation; + return this.capabilities?.orientation; } async windowSize() { @@ -44,13 +50,17 @@ export default class MobileMetaData { } async screenResolution() { - const data = await this.driver.executeScript({ script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];', args: [] }); - const screenInfo = data.value; - return `${screenInfo[0]} x ${screenInfo[1]}`; + return await Cache.withCache(Cache.resolution, this.driver.sessionId, async () => { + 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]}`; + }); } async devicePixelRatio() { - const devicePixelRatio = await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }); - return devicePixelRatio.value; + return await Cache.withCache(Cache.dpr, this.driver.sessionId, async () => { + const devicePixelRatio = await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }); + return devicePixelRatio.value; + }); } } diff --git a/packages/webdriver-utils/src/metadata/normalizeData.js b/packages/webdriver-utils/src/metadata/normalizeData.js new file mode 100644 index 000000000..749e1edef --- /dev/null +++ b/packages/webdriver-utils/src/metadata/normalizeData.js @@ -0,0 +1,34 @@ +export default class NormalizeData { + osRollUp(os) { + if (os.toLowerCase().startsWith('win')) { + return 'Windows'; + } else if (os.toLowerCase().startsWith('mac')) { + return 'OS X'; + } else if (os.toLowerCase().includes('iphone') || os.toLowerCase().startsWith('ios')) { + return 'iOS'; + } else if (os.toLowerCase().startsWith('android')) { + return 'Android'; + } + return os; + } + + browserRollUp(browserName, device) { + if (device) { + if (browserName?.toLowerCase().includes('chrome')) { + return 'chrome'; + } else if ((browserName?.toLowerCase().includes('iphone') || + browserName?.toLowerCase().includes('ipad'))) { + return 'safari'; + } + } + return browserName; + } + + browserVersionRollUp(browserVersion, deviceName, device) { + if (device) { + // return `${this.osRollUp(os)} ${osVersion?.split('.')[0]}`; + return deviceName; + } + return browserVersion?.split('.')[0]; + } +} diff --git a/packages/webdriver-utils/src/providers/automateProvider.js b/packages/webdriver-utils/src/providers/automateProvider.js index c82b8e163..db98af8cd 100644 --- a/packages/webdriver-utils/src/providers/automateProvider.js +++ b/packages/webdriver-utils/src/providers/automateProvider.js @@ -2,6 +2,7 @@ import utils from '@percy/sdk-utils'; import GenericProvider from './genericProvider.js'; import Cache from '../util/cache.js'; import Tile from '../util/tile.js'; +import NormalizeData from '../metadata/normalizeData.js'; const log = utils.logger('webdriver-utils:automateProvider'); const { TimeIt } = utils; @@ -28,6 +29,7 @@ export default class AutomateProvider extends GenericProvider { buildInfo ); this._markedPercy = false; + this.automateResults = null; } static supports(commandExecutorUrl) { @@ -42,12 +44,13 @@ export default class AutomateProvider extends GenericProvider { }) { let response = null; let error; - log.info('Preparing to capture screenshots on automate ...'); + log.debug(`[${name}] : Preparing to capture screenshots on automate ...`); try { - log.debug('Marking automate session as percy ...'); - let result = await this.percyScreenshotBegin(name); - log.debug('Fetching the debug url ...'); - this.setDebugUrl(result); + log.debug(`[${name}] : Marking automate session as percy ...`); + const result = await this.percyScreenshotBegin(name); + this.automateResults = JSON.parse(result.value); + log.debug(`[${name}] : Fetching the debug url ...`); + this.setDebugUrl(); response = await super.screenshot(name, { ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions }); } catch (e) { error = e; @@ -70,8 +73,8 @@ export default class AutomateProvider extends GenericProvider { this._markedPercy = result.success; return result; } catch (e) { - log.debug(`[${name}] Could not mark Automate session as percy`); - log.error(`[${name}] error: ${e.toString()}`); + log.debug(`[${name}] : Could not mark Automate session as percy`); + log.error(`[${name}] : error: ${e.toString()}`); return null; } }); @@ -88,7 +91,7 @@ export default class AutomateProvider extends GenericProvider { state: 'end' }); } catch (e) { - log.debug(`[${name}] Could not execute percyScreenshot command for Automate`); + log.debug(`[${name}] : Could not execute percyScreenshot command for Automate`); log.error(e); } }); @@ -96,14 +99,14 @@ export default class AutomateProvider extends GenericProvider { async getTiles(headerHeight, footerHeight, fullscreen) { if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().'); - log.info('Starting actual screenshotting phase'); + log.debug('Starting actual screenshotting phase'); const response = await TimeIt.run('percyScreenshot:screenshot', async () => { return await this.browserstackExecutor('percyScreenshot', { state: 'screenshot', percyBuildId: this.buildInfo.id, screenshotType: 'singlepage', - scaleFactor: await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }), + scaleFactor: await this.metaData.devicePixelRatio(), options: this.options }); }); @@ -142,8 +145,45 @@ export default class AutomateProvider extends GenericProvider { if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().'); this.debugUrl = await Cache.withCache(Cache.bstackSessionDetails, this.driver.sessionId, async () => { - const sessionDetails = await this.browserstackExecutor('getSessionDetails'); - return JSON.parse(sessionDetails.value).browser_url; + return `https://automate.browserstack.com/builds/${this.automateResults.buildHash}/sessions/${this.automateResults.sessionHash}`; }); } + + async getTag() { + if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().'); + if (!this.automateResults) throw new Error('Comparison tag details not available'); + + const automateCaps = this.automateResults.capabilities; + const normalizeTags = new NormalizeData(); + + let deviceName = this.automateResults.deviceName; + const osName = normalizeTags.osRollUp(automateCaps.os); + const osVersion = automateCaps.os_version?.split('.')[0]; + const browserName = normalizeTags.browserRollUp(automateCaps.browserName, this.metaData.device()); + const browserVersion = normalizeTags.browserVersionRollUp(automateCaps.browserVersion, deviceName, this.metaData.device()); + + if (!this.metaData.device()) { + deviceName = `${osName}_${osVersion}_${browserName}_${browserVersion}`; + } + + let { width, height } = await this.metaData.windowSize(); + 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, + osVersion, + width, + height, + orientation, + browserName, + browserVersion, + resolution + }; + } } diff --git a/packages/webdriver-utils/src/providers/genericProvider.js b/packages/webdriver-utils/src/providers/genericProvider.js index ef801971a..784727bff 100644 --- a/packages/webdriver-utils/src/providers/genericProvider.js +++ b/packages/webdriver-utils/src/providers/genericProvider.js @@ -69,7 +69,7 @@ export default class GenericProvider { } async createDriver() { - this.driver = new Driver(this.sessionId, this.commandExecutorUrl); + this.driver = new Driver(this.sessionId, this.commandExecutorUrl, this.capabilities); log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`); const caps = await this.driver.getCapabilites(); log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`); @@ -117,21 +117,21 @@ export default class GenericProvider { this.addDefaultOptions(); const percyCSS = (this.defaultPercyCSS() + (this.options.percyCSS || '')).split('\n').join(''); - log.debug(`Applying the percyCSS - ${this.options.percyCSS}`); + log.debug(`[${name}] : Applying the percyCSS - ${this.options.percyCSS}`); await this.addPercyCSS(percyCSS); log.debug('Fetching comparisong tag ...'); const tag = await this.getTag(); - log.debug(`${name} : Tag ${JSON.stringify(tag)}`); + log.debug(`[${name}] : Tag ${JSON.stringify(tag)}`); const tiles = await this.getTiles(this.header, this.footer, fullscreen); - log.debug(`${name} : Tiles ${JSON.stringify(tiles)}`); + log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`); const ignoreRegions = await this.findIgnoredRegions( ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions ); await this.setDebugUrl(); - log.debug(`${name} : Debug url ${this.debugUrl}`); + log.debug(`[${name}] : Debug url ${this.debugUrl}`); await this.removePercyCSS(); return { @@ -293,13 +293,12 @@ export default class GenericProvider { return ignoredElementsArray; } - async getHeaderFooter() { + async getHeaderFooter(deviceName, osVersion, browserName) { // passing 0 as key, since across different pages and tests, this config will remain same const devicesConfig = await Cache.withCache(Cache.devicesConfig, 0, async () => { return (await request(DEVICES_CONFIG_URL)).body; }); - let deviceKey = `${this.metaData.deviceName()}-${this.metaData.osVersion()}`; - let browserName = this.capabilities.browserName; + let deviceKey = `${deviceName}-${osVersion}`; return devicesConfig[deviceKey] ? ( devicesConfig[deviceKey][browserName] diff --git a/packages/webdriver-utils/src/util/cache.js b/packages/webdriver-utils/src/util/cache.js index e282f5717..640d849bb 100644 --- a/packages/webdriver-utils/src/util/cache.js +++ b/packages/webdriver-utils/src/util/cache.js @@ -10,6 +10,8 @@ export default class Cache { static bstackSessionDetails = 'bstackSessionDetails'; static systemBars = 'systemBars'; static devicesConfig = 'devicesConfig'; + static dpr = 'dpr'; + static resolution = 'resolution'; // maintainance static lastTime = Date.now(); diff --git a/packages/webdriver-utils/test/driver.test.js b/packages/webdriver-utils/test/driver.test.js index 2c95aa134..c043615ad 100644 --- a/packages/webdriver-utils/test/driver.test.js +++ b/packages/webdriver-utils/test/driver.test.js @@ -10,13 +10,17 @@ describe('Driver', () => { }; let sessionId = '123'; let executorUrl = 'http://localhost/wd/hub'; + let passedCapabilities = { + browser: 'chrome', + platform: 'win' + }; let driver; beforeEach(() => { requestSpy = spyOn(utils.request, 'fetch').and.returnValue( Promise.resolve(mockResponseObject) ); - driver = new Driver(sessionId, executorUrl); + driver = new Driver(sessionId, executorUrl, passedCapabilities); }); describe('constructor', () => { @@ -34,6 +38,29 @@ describe('Driver', () => { }); }); + describe('getCapabilities fallback', () => { + const mockFailedResponse = { + body: '{"value": {"message" : "Internal Server Error"}', + status: 500, + headers: { 'content-type': 'application/text' } + }; + let requestFailedSpy; + const sessionId = '1234'; + const newDriver = new Driver(sessionId, executorUrl, passedCapabilities); + + beforeEach(() => { + requestFailedSpy = spyOn(utils.request, 'fetch').and.returnValue( + Promise.resolve(mockFailedResponse) + ); + }); + + it('falls back to passed capabilites', async () => { + let res = await newDriver.getCapabilites(); + expect(requestFailedSpy).toHaveBeenCalledOnceWith(`${executorUrl}/session/${sessionId}`, Object({})); + expect(res).toBe(passedCapabilities); + }); + }); + describe('getWindowsize', () => { it('calls requests', async () => { let res = await driver.getWindowSize(); diff --git a/packages/webdriver-utils/test/metadata/desktopMetaData.test.js b/packages/webdriver-utils/test/metadata/desktopMetaData.test.js index 54d837371..9bec6dc39 100644 --- a/packages/webdriver-utils/test/metadata/desktopMetaData.test.js +++ b/packages/webdriver-utils/test/metadata/desktopMetaData.test.js @@ -111,4 +111,10 @@ describe('DesktopMetaData', () => { .toHaveBeenCalledWith({ script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];', args: [] }); }); }); + + describe('device', () => { + it('returns false', () => { + expect(desktopMetaData.device()).toEqual(false); + }); + }); }); diff --git a/packages/webdriver-utils/test/metadata/mobileMetaData.test.js b/packages/webdriver-utils/test/metadata/mobileMetaData.test.js index f8dd9b187..418cb9a45 100644 --- a/packages/webdriver-utils/test/metadata/mobileMetaData.test.js +++ b/packages/webdriver-utils/test/metadata/mobileMetaData.test.js @@ -1,5 +1,6 @@ import MobileMetaData from '../../src/metadata/mobileMetaData.js'; import Driver from '../../src/driver.js'; +import Cache from '../../src/util/cache.js'; describe('MobileMetaData', () => { let getWindowSizeSpy; @@ -9,6 +10,7 @@ describe('MobileMetaData', () => { beforeEach(() => { getWindowSizeSpy = spyOn(Driver.prototype, 'getWindowSize'); executeScriptSpy = spyOn(Driver.prototype, 'executeScript'); + Cache.reset(); mobileMetaData = new MobileMetaData(new Driver('123', 'http:executorUrl'), { osVersion: '12.0', browserName: 'Chrome', @@ -125,7 +127,13 @@ describe('MobileMetaData', () => { screenInfo = await mobileMetaData.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: [] }); + }); + }); + + describe('device', () => { + it('returns false', () => { + expect(mobileMetaData.device()).toEqual(true); }); }); }); diff --git a/packages/webdriver-utils/test/metadata/normalizeData.test.js b/packages/webdriver-utils/test/metadata/normalizeData.test.js new file mode 100644 index 000000000..6da620be9 --- /dev/null +++ b/packages/webdriver-utils/test/metadata/normalizeData.test.js @@ -0,0 +1,84 @@ +import NormalizeData from '../../src/metadata/normalizeData.js'; + +describe('NormalizeData', () => { + let normalizeDataObj; + beforeAll(() => { + normalizeDataObj = new NormalizeData(); + }); + + afterAll(() => { + normalizeDataObj = null; + }); + + describe('osRollUp', () => { + it('should rollup windows os accordingly', () => { + expect(normalizeDataObj.osRollUp('win8.1')).toBe('Windows'); + expect(normalizeDataObj.osRollUp('win8')).toBe('Windows'); + expect(normalizeDataObj.osRollUp('win7')).toBe('Windows'); + expect(normalizeDataObj.osRollUp('win11')).toBe('Windows'); + expect(normalizeDataObj.osRollUp('Win10')).toBe('Windows'); + expect(normalizeDataObj.osRollUp('Windows')).toBe('Windows'); + }); + + it('should rollup mac os accordingly', () => { + expect(normalizeDataObj.osRollUp('mac')).toBe('OS X'); + expect(normalizeDataObj.osRollUp('macos')).toBe('OS X'); + expect(normalizeDataObj.osRollUp('Mac')).toBe('OS X'); + }); + + it('should rollup iphone os accordingly', () => { + expect(normalizeDataObj.osRollUp('iphone')).toBe('iOS'); + expect(normalizeDataObj.osRollUp('iphone8.1')).toBe('iOS'); + expect(normalizeDataObj.osRollUp('ios')).toBe('iOS'); + }); + + it('should rollup android os accordingly', () => { + expect(normalizeDataObj.osRollUp('android')).toBe('Android'); + expect(normalizeDataObj.osRollUp('android8.1')).toBe('Android'); + }); + + it('should return any other os as it is', () => { + expect(normalizeDataObj.osRollUp('fireOS')).toBe('fireOS'); + }); + }); + + describe('browserRollUp', () => { + describe('should rollup browsers for devices', () => { + it('should roll up android to chrome', () => { + expect(normalizeDataObj.browserRollUp('chrome_android', true)).toBe('chrome'); + }); + + it('should roll up iPhone/iPad to safari', () => { + expect(normalizeDataObj.browserRollUp('iPhone', true)).toBe('safari'); + expect(normalizeDataObj.browserRollUp('iPad', true)).toBe('safari'); + }); + + it('should return default if no condition is matched', () => { + expect(normalizeDataObj.browserRollUp('abc', true)).toBe('abc'); + }); + }); + + describe('should return browsers as it is for desktop', () => { + it('should return the browser as detected', () => { + expect(normalizeDataObj.browserRollUp('Chrome', false)).toBe('Chrome'); + }); + }); + }); + + describe('browserVersionRollUp', () => { + describe('should rollup browser version for devices', () => { + it('should roll up version to device_name', () => { + expect(normalizeDataObj.browserVersionRollUp('Samsung Galaxy S21', 'Samsung Galaxy S21', true)).toBe('Samsung Galaxy S21'); + expect(normalizeDataObj.browserVersionRollUp('iphone', 'iPhone 12 Pro', true)).toBe('iPhone 12 Pro'); + expect(normalizeDataObj.browserVersionRollUp('ipad', 'iPad 12 2022', true)).toBe('iPad 12 2022'); + }); + }); + + describe('should return major browser version as it is for desktop', () => { + it('should return the browser version as detected', () => { + expect(normalizeDataObj.browserVersionRollUp('114.0.1.2', 'x.x.x.x', false)).toBe('114'); + expect(normalizeDataObj.browserVersionRollUp('16.5', 'x.x.x.x', false)).toBe('16'); + }); + }); + }); +}); diff --git a/packages/webdriver-utils/test/providers/automateProvider.test.js b/packages/webdriver-utils/test/providers/automateProvider.test.js index e4f5326fd..8d6bef233 100644 --- a/packages/webdriver-utils/test/providers/automateProvider.test.js +++ b/packages/webdriver-utils/test/providers/automateProvider.test.js @@ -1,7 +1,9 @@ import Driver from '../../src/driver.js'; import GenericProvider from '../../src/providers/genericProvider.js'; 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'; describe('AutomateProvider', () => { let superScreenshotSpy; @@ -50,24 +52,25 @@ describe('AutomateProvider', () => { }); describe('setDebugUrl', () => { - let browserstackExecutorSpy; + let percyScreenshotBeginSpy; let percyBuildInfo = { id: '123', url: 'https://percy.io/abc/123' }; beforeEach(async () => { + percyScreenshotBeginSpy = spyOn(AutomateProvider.prototype, + 'percyScreenshotBegin').and.returnValue(Promise.resolve({ value: '{"buildHash":"12e3","sessionHash":"abc1d"}' })); spyOn(Driver.prototype, 'getCapabilites'); - browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor') - .and.returnValue(Promise.resolve({ value: '{"browser_url": "http:localhost"}' })); }); - it('calls browserstackExecutor getSessionDetails', async () => { + it('sets automate url', async () => { let automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, {}, 'client', 'environment', {}, percyBuildInfo); await automateProvider.createDriver(); - await automateProvider.setDebugUrl(); - expect(browserstackExecutorSpy).toHaveBeenCalledWith('getSessionDetails'); - expect(automateProvider.debugUrl).toEqual('http:localhost'); + await automateProvider.screenshot('abc', { }); + + expect(percyScreenshotBeginSpy).toHaveBeenCalledWith('abc'); + expect(automateProvider.debugUrl).toEqual('https://automate.browserstack.com/builds/12e3/sessions/abc1d'); }); it('throws error if driver is not initialized', async () => { @@ -99,7 +102,7 @@ describe('AutomateProvider', () => { beforeEach(async () => { percyScreenshotBeginSpy = spyOn(AutomateProvider.prototype, - 'percyScreenshotBegin').and.returnValue(true); + 'percyScreenshotBegin').and.returnValue({ value: '{"buildHash":"12e3","sessionHash":"abc1d"}' }); percyScreenshotEndSpy = spyOn(AutomateProvider.prototype, 'percyScreenshotEnd').and.returnValue(true); spyOn(Driver.prototype, 'getCapabilites'); @@ -226,4 +229,113 @@ describe('AutomateProvider', () => { await expectAsync(automateProvider.getTiles(false)).toBeRejectedWithError('Driver is null, please initialize driver with createDriver().'); }); }); + + describe('getTag', () => { + let percyScreenshotBeginSpy; + let windowSizeSpy; + let orientationSpy; + let resolutionSpy; + let getHeaderFooterSpy; + let percyBuildInfo = { + id: '123', + url: 'https://percy.io/abc/123' + }; + + describe('for desktop', () => { + const automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, {}, 'client', 'environment', {}, percyBuildInfo); + beforeEach(async () => { + 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') + .and.returnValue('1980 x 1080'); + orientationSpy = spyOn(DesktopMetaData.prototype, 'orientation') + .and.returnValue('landscape'); + }); + + it('generates comparison tag for desktop', async () => { + await automateProvider.createDriver(); + await automateProvider.screenshot('abc', { }); + + const res = await automateProvider.getTag(); + + expect(percyScreenshotBeginSpy).toHaveBeenCalledWith('abc'); + 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', + osVersion: '11', + width: 1000, + height: 1000, + orientation: 'landscape', + browserName: 'chrome', + browserVersion: '113', + resolution: '1980 x 1080' + }); + }); + }); + + describe('for devices', () => { + const automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'android' }, {}, {}, 'client', 'environment', {}, percyBuildInfo); + beforeEach(async () => { + 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') + .and.returnValue('1980 x 1080'); + orientationSpy = spyOn(MobileMetaData.prototype, 'orientation') + .and.returnValue(undefined); + }); + + it('generates comparsion tag for mobile', async () => { + await automateProvider.createDriver(); + await automateProvider.screenshot('abc', { }); + + const res = await automateProvider.getTag(); + + expect(percyScreenshotBeginSpy).toHaveBeenCalledWith('abc'); + 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', + osVersion: '11', + width: 1000, + height: 1000, + orientation: 'portrait', + browserName: 'chrome', + browserVersion: 'Samsung Galaxy S21', + resolution: '1980 x 1080' + }); + }); + }); + + describe('driver is null', () => { + it('throws Error when called without initializing driver', async () => { + let automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, {}, 'client', 'environment', {}, percyBuildInfo); + await expectAsync(automateProvider.getTag()) + .toBeRejectedWithError('Driver is null, please initialize driver with createDriver().'); + }); + }); + + describe('automateResults is null', () => { + it('throws Error automateResults are not available', async () => { + let automateProvider = new AutomateProvider('1234', 'https://localhost/command-executor', { platform: 'win' }, {}, {}, 'client', 'environment', {}, percyBuildInfo); + await automateProvider.createDriver(); + await expectAsync(automateProvider.getTag()) + .toBeRejectedWithError('Comparison tag details not available'); + }); + }); + }); }); diff --git a/packages/webdriver-utils/test/providers/genericProvider.test.js b/packages/webdriver-utils/test/providers/genericProvider.test.js index 64b4cbf02..aed465622 100644 --- a/packages/webdriver-utils/test/providers/genericProvider.test.js +++ b/packages/webdriver-utils/test/providers/genericProvider.test.js @@ -21,7 +21,7 @@ describe('GenericProvider', () => { beforeEach(() => { metaDataResolverSpy = spyOn(MetaDataResolver, 'resolve'); - expectedDriver = new Driver('123', 'http:executorUrl'); + expectedDriver = new Driver('123', 'http:executorUrl', {}); }); it('creates driver', async () => { @@ -431,7 +431,7 @@ describe('GenericProvider', () => { spyOn(utils.request, 'fetch').and.returnValue( Promise.resolve(mockResponseObject) ); - const [header, footer] = await provider.getHeaderFooter(); + const [header, footer] = await provider.getHeaderFooter('iPhone 12 Pro', '13', 'safari'); expect(header).toEqual(141); expect(footer).toEqual(399); }); @@ -439,17 +439,12 @@ describe('GenericProvider', () => { it('should return 0,0 for unmatched device name', async () => { await provider.createDriver(); let mockResponseObject = { - 'iPhone 13 Pro-14': { - safari: { - header: 141, - footer: 399 - } - } + 'iPhone 13 Pro-14': {} }; spyOn(Cache, 'withCache').and.returnValue( Promise.resolve(mockResponseObject) ); - const [header, footer] = await provider.getHeaderFooter(); + const [header, footer] = await provider.getHeaderFooter('iPhone 13 Pro', '14', 'safari'); expect(header).toEqual(0); expect(footer).toEqual(0); });