Skip to content

Commit

Permalink
feat: added support for extracting and passing header and footer heig…
Browse files Browse the repository at this point in the history
…hts (#1305)

* feat: added support for extracting and passing header and footer heights

* test: getHeaderFooter

* chore: lint using fix

* fix: use header and footer height for calculating tag

* test: updated percy-cli tests

* test: update expectations for header and footer heights for getTiles;

* feat: updated getTiles function to use header and footer params, so that it is more testable

* feat: update comparison tag logic

* test: updated getTag spec

* chore: lint using yarn

* feat: add support for header and footer extraction;

* test: update test for extracting header and footer

* chore: lint using fix

* fix: use metadata.osName to detect android

* test: update generaticProvider tests

* chore: added some logging

* fix: webdriver-utils/index logging

* chore: lint using fix

* feat: added caching support for devicesJson

* chore: lint using fix

* test: updated getHeaderFooter spec

* chore: lint fix

* feat: change desktop deviceName format

* test: increase coverage

* chore: add comments around skipping request.test from browser tests

* chore: add webdriver-utils as dependency for core package

* chore: resolve TODO comments
  • Loading branch information
nilshah98 authored Jul 14, 2023
1 parent eec86a3 commit 079afd9
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 26 deletions.
1 change: 1 addition & 0 deletions karma.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = async config => {
{ pattern: 'test/**/*.test.js', type: 'module', watched: false },
{ pattern: 'test/assets/**', watched: false, included: false }
],
// NOTE: Although sdk-utils test run in browser as well, we do not run sdk-utils/request test in browsers as we require creation of https server for this test
exclude: [
'**/test/request.test.js',
],
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@percy/config": "1.27.0-alpha.0",
"@percy/dom": "1.27.0-alpha.0",
"@percy/logger": "1.27.0-alpha.0",
"@percy/webdriver-utils": "1.27.0-alpha.0",
"content-disposition": "^0.5.4",
"cross-spawn": "^7.0.3",
"extract-zip": "^2.0.1",
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { createRequire } from 'module';
import logger from '@percy/logger';
import { normalize } from '@percy/config/utils';
import { getPackageJSON, Server, percyAutomateRequestHandler } from './utils.js';
// TODO Remove below esline disable once we publish webdriver-util
import WebdriverUtils from '@percy/webdriver-utils'; // eslint-disable-line import/no-extraneous-dependencies

import WebdriverUtils from '@percy/webdriver-utils';
// need require.resolve until import.meta.resolve can be transpiled
export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom');

Expand Down
1 change: 1 addition & 0 deletions packages/sdk-utils/test/request.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import http from 'http';
import fs from 'fs';
import path from 'path';

// NOTE: Although sdk-utils test run in browser as well, we do not run sdk-utils/request test in browsers as we require creation of https server for this test
const ssl = {
cert: fs.readFileSync(path.join(__dirname, 'assets', 'certs', 'test.crt')),
key: fs.readFileSync(path.join(__dirname, 'assets', 'certs', 'test.key'))
Expand Down
4 changes: 3 additions & 1 deletion packages/webdriver-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ export default class WebdriverUtils {
}

async automateScreenshot() {
this.log.info('Starting automate screenshot');
this.log.info('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 ...');
await automate.createDriver();
this.log.debug('Created driver ...');
return await automate.screenshot(this.snapshotName, this.options);
}
}
2 changes: 1 addition & 1 deletion packages/webdriver-utils/src/metadata/desktopMetaData.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class DesktopMetaData {

// combination of browserName + browserVersion + osVersion + osName
deviceName() {
return this.browserName() + '_' + this.browserVersion() + '_' + this.osVersion() + '_' + this.osName();
return this.osName() + '_' + this.osVersion() + '_' + this.browserName() + '_' + this.browserVersion();
}

orientation() {
Expand Down
14 changes: 10 additions & 4 deletions packages/webdriver-utils/src/providers/automateProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ export default class AutomateProvider extends GenericProvider {
}) {
let response = null;
let error;
log.info('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);
response = await super.screenshot(name, { ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions });
} catch (e) {
Expand Down Expand Up @@ -85,13 +88,15 @@ export default class AutomateProvider extends GenericProvider {
state: 'end'
});
} catch (e) {
log.debug(`[${name}] Could not mark Automate session as percy`);
log.debug(`[${name}] Could not execute percyScreenshot command for Automate`);
log.error(e);
}
});
}

async getTiles(fullscreen) {
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');

const response = await TimeIt.run('percyScreenshot:screenshot', async () => {
return await this.browserstackExecutor('percyScreenshot', {
Expand All @@ -111,13 +116,14 @@ export default class AutomateProvider extends GenericProvider {

const tiles = [];
const tileResponse = JSON.parse(responseValue.result);
log.debug('Tiles captured successfully');

for (let tileData of tileResponse.sha) {
tiles.push(new Tile({
statusBarHeight: 0,
navBarHeight: 0,
headerHeight: 0,
footerHeight: 0,
headerHeight,
footerHeight,
fullscreen,
sha: tileData.split('-')[0] // drop build id
}));
Expand Down
53 changes: 41 additions & 12 deletions packages/webdriver-utils/src/providers/genericProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import utils from '@percy/sdk-utils';
import MetaDataResolver from '../metadata/metaDataResolver.js';
import Tile from '../util/tile.js';
import Driver from '../driver.js';
import Cache from '../util/cache.js';
const { request } = utils;

const DEVICES_CONFIG_URL = 'https://storage.googleapis.com/percy-utils/devices.json';
const log = utils.logger('webdriver-utils:genericProvider');

export default class GenericProvider {
Expand Down Expand Up @@ -31,6 +34,8 @@ export default class GenericProvider {
this.driver = null;
this.metaData = null;
this.debugUrl = null;
this.header = 0;
this.footer = 0;
}

addDefaultOptions() {
Expand Down Expand Up @@ -65,7 +70,9 @@ export default class GenericProvider {

async createDriver() {
this.driver = new Driver(this.sessionId, this.commandExecutorUrl);
log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`);
const caps = await this.driver.getCapabilites();
log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`);
this.metaData = await MetaDataResolver.resolve(this.driver, caps, this.capabilities);
}

Expand Down Expand Up @@ -110,19 +117,23 @@ export default class GenericProvider {
this.addDefaultOptions();

const percyCSS = (this.defaultPercyCSS() + (this.options.percyCSS || '')).split('\n').join('');
log.debug(`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)}`);

const tiles = await this.getTiles(this.header, this.footer, fullscreen);
log.debug(`${name} : Tiles ${JSON.stringify(tiles)}`);

const tiles = await this.getTiles(fullscreen);
const ignoreRegions = await this.findIgnoredRegions(
ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions
);
await this.setDebugUrl();
await this.removePercyCSS();

log.debug(`${name} : Tag ${JSON.stringify(tag)}`);
log.debug(`${name} : Tiles ${JSON.stringify(tiles)}`);
log.debug(`${name} : Debug url ${this.debugUrl}`);

await this.removePercyCSS();
return {
name,
tag,
Expand All @@ -142,31 +153,34 @@ export default class GenericProvider {
return 'dummyValue';
}

async getTiles(fullscreen) {
async getTiles(headerHeight, footerHeight, 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');
return {
tiles: [
new Tile({
content: base64content,
// TODO: Need to add method to fetch these attr
statusBarHeight: 0,
navBarHeight: 0,
headerHeight: 0,
footerHeight: 0,
headerHeight,
footerHeight,
fullscreen
})
],
// TODO: Add Generic support sha for contextual diff
// TODO: Add Generic support sha for contextual diff for non-automate
domInfoSha: await this.getDomContent()
};
}

async getTag() {
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
const { width, height } = await this.metaData.windowSize();
let { width, height } = await this.metaData.windowSize();
const resolution = await this.metaData.screenResolution();
const orientation = this.metaData.orientation();
[this.header, this.footer] = await this.getHeaderFooter();
// for android window size only constitutes of browser viewport, hence adding nav / status / url bar heights
height = this.metaData.osName() === 'android' ? height + this.header + this.footer : height;
return {
name: this.metaData.deviceName(),
osName: this.metaData.osName(),
Expand All @@ -180,7 +194,7 @@ export default class GenericProvider {
};
}

// TODO: Add Debugging Url
// TODO: Add Debugging Url for non-automate
async setDebugUrl() {
this.debugUrl = 'https://localhost/v1';
}
Expand Down Expand Up @@ -278,4 +292,19 @@ export default class GenericProvider {
}
return ignoredElementsArray;
}

async getHeaderFooter() {
// 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;
return devicesConfig[deviceKey]
? (
devicesConfig[deviceKey][browserName]
? [devicesConfig[deviceKey][browserName].header, devicesConfig[deviceKey][browserName].footer]
: [0, 0]
) : [0, 0];
}
}
1 change: 1 addition & 0 deletions packages/webdriver-utils/src/util/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class Cache {
static caps = 'caps';
static bstackSessionDetails = 'bstackSessionDetails';
static systemBars = 'systemBars';
static devicesConfig = 'devicesConfig';

// maintainance
static lastTime = Date.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('DesktopMetaData', () => {

describe('deviceName', () => {
it('calculates deviceName', () => {
expect(desktopMetaData.deviceName()).toEqual('chrome_111_10_win');
expect(desktopMetaData.deviceName()).toEqual('win_10_chrome_111');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ describe('AutomateProvider', () => {

beforeEach(async () => {
spyOn(Driver.prototype, 'getCapabilites');
spyOn(GenericProvider.prototype, 'getHeaderFooter').and.returnValue(Promise.resolve([123, 456]));
browserstackExecutorSpy = spyOn(AutomateProvider.prototype, 'browserstackExecutor')
.and.returnValue(Promise.resolve({ value: '{ "result": "{\\"dom_sha\\": \\"abc\\", \\"sha\\": [\\"abc-1\\", \\"xyz-2\\"]}", "success":true }' }));
executeScriptSpy = spyOn(Driver.prototype, 'executeScript')
Expand All @@ -197,7 +198,7 @@ describe('AutomateProvider', () => {

it('should return tiles when success', async () => {
await automateProvider.createDriver();
const res = await automateProvider.getTiles(false);
const res = await automateProvider.getTiles(123, 456, false);
expect(browserstackExecutorSpy).toHaveBeenCalledTimes(1);
expect(executeScriptSpy).toHaveBeenCalledTimes(1);
expect(Object.keys(res).length).toEqual(2);
Expand All @@ -207,6 +208,10 @@ describe('AutomateProvider', () => {
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);
});

it('throws error when response is false', async () => {
Expand Down
Loading

0 comments on commit 079afd9

Please sign in to comment.