Skip to content

Commit

Permalink
Adding Optimizations for Screenshot and all
Browse files Browse the repository at this point in the history
  • Loading branch information
Amit3200 committed Jun 4, 2023
1 parent d365dc3 commit 4dd8743
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 20 deletions.
3 changes: 2 additions & 1 deletion packages/client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ export class PercyClient {

async createComparison(snapshotId, { tag, tiles = [], externalDebugUrl, ignoredElementsData } = {}) {
validateId('snapshot', snapshotId);

// Remove post percy api deploy
externalDebugUrl = externalDebugUrl.replace('automate', 'app-automate');
this.log.debug(`Creating comparision: ${tag.name}...`);

for (let tile of tiles) {
Expand Down
17 changes: 14 additions & 3 deletions packages/core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,20 @@ export function createPercyServer(percy, port) {
.route('post', '/percy/flush', async (req, res) => res.json(200, {
success: await percy.flush(req.body).then(() => true)
}))
.route('post', '/percy/automateScreenshot', async (req, res) => res.json(200, {
success: await (percy.upload(await new WebdriverUtils(req.body).automateScreenshot())).then(() => true)
}))
.route('post', '/percy/automateScreenshot', async (req, res) => {
if (req.body.client_info) {
req.body.clientInfo = req.body.client_info;
}
if (req.body.environment_info) {
req.body.environmentInfo = req.body.environment_info;
}
if (!req.body.options) {
req.body.options = {};
}
res.json(200, {
success: await (percy.upload(await new WebdriverUtils(req.body).automateScreenshot())).then(() => true)
});
})
// stops percy at the end of the current event loop
.route('/percy/stop', (req, res) => {
setImmediate(() => percy.stop());
Expand Down
7 changes: 5 additions & 2 deletions packages/webdriver-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import utils from '@percy/sdk-utils';

export default class WebdriverUtils {
log = utils.logger('webdriver-utils:main');
constructor({ sessionId, commandExecutorUrl, capabilities, sessionCapabilites, snapshotName }) {
constructor({ sessionId, commandExecutorUrl, capabilities, sessionCapabilites, snapshotName, clientInfo, environmentInfo, options }) {
this.sessionId = sessionId;
this.commandExecutorUrl = commandExecutorUrl;
this.capabilities = capabilities;
this.sessionCapabilites = sessionCapabilites;
this.snapshotName = snapshotName;
this.clientInfo = clientInfo;
this.environmentInfo = environmentInfo;
this.options = options;
}

async automateScreenshot() {
this.log.info('Starting automate screenshot');
const automate = ProviderResolver.resolve(this.sessionId, this.commandExecutorUrl, this.capabilities, this.sessionCapabilites);
const automate = ProviderResolver.resolve(this.sessionId, this.commandExecutorUrl, this.capabilities, this.sessionCapabilites, this.clientInfo, this.environmentInfo, this.options);
await automate.createDriver();
return await automate.screenshot(this.snapshotName);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/webdriver-utils/src/metadata/desktopMetaData.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,26 @@ export default class DesktopMetaData {
return this.capabilities.browserName.toLowerCase();
}

browserVersion() {
return this.capabilities.browserVersion.split('.')[0];
}

osName() {
let osName = this.capabilities.osVersion;
let osName = this.capabilities.os;
if (osName) return osName.toLowerCase();

osName = this.capabilities.platform;
return osName;
}

// desktop will show this as browser version
// showing major version
osVersion() {
return this.capabilities.version.split('.')[0];
return this.capabilities.osVersion.toLowerCase();
}

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

orientation() {
Expand Down
111 changes: 111 additions & 0 deletions packages/webdriver-utils/src/providers/automateProvider.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,122 @@
import utils from '@percy/sdk-utils';
import GenericProvider from './genericProvider.js';
import Cache from '../util/cache.js';
import Tile from '../util/tile.js';
import TimeIt from '../util/timing.js';

const log = utils.logger('webdriver-utils:automateProvider');
export default class AutomateProvider extends GenericProvider {
constructor(
sessionId,
commandExecutorUrl,
capabilities,
sessionCapabilites,
clientInfo,
environmentInfo,
options
) {
super(
sessionId,
commandExecutorUrl,
capabilities,
sessionCapabilites,
clientInfo,
environmentInfo,
options
);
this._markedPercy = false;
}

static supports(commandExecutorUrl) {
return commandExecutorUrl.includes(process.env.AA_DOMAIN || 'browserstack');
}

async screenshot(name) {
let response = null;
let error;
try {
let result = await this.percyScreenshotBegin(name);
this.setDebugUrl(result);
response = await super.screenshot(name);
} catch (e) {
error = e;
throw e;
} finally {
await this.percyScreenshotEnd(name, response?.body?.link, `${error}`);
}
return response;
}

async percyScreenshotBegin(name) {
return await TimeIt.run('percyScreenshotBegin', async () => {
try {
let result = await this.browserstackExecutor('percyScreenshot', {
name,
percyBuildId: process.env.PERCY_BUILD_ID,
percyBuildUrl: process.env.PERCY_BUILD_URL,
state: 'begin'
});
this._markedPercy = result.success;
return result;
} catch (e) {
log.debug(`[${name}] Could not mark Automate session as percy`);
return null;
}
});
}

async percyScreenshotEnd(name, percyScreenshotUrl, statusMessage = null) {
return await TimeIt.run('percyScreenshotEnd', async () => {
try {
await this.browserstackExecutor('percyScreenshot', {
name,
percyScreenshotUrl,
status: percyScreenshotUrl ? 'success' : 'failure',
statusMessage,
state: 'end'
});
} catch (e) {
log.debug(`[${name}] Could not mark App Automate session as percy`);
}
});
}

async getTiles(fullscreen) {
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');

const response = await TimeIt.run('percyScreenshot:screenshot', async () => {
return await this.browserstackExecutor('percyScreenshot', {
state: 'screenshot',
percyBuildId: process.env.PERCY_BUILD_ID,
screenshotType: 'singlepage',
scaleFactor: await this.driver.executeScript({ script: 'return window.devicePixelRatio;', args: [] }),
options: this.options
});
});

const responseValue = JSON.parse(response.value);
if (!responseValue.success) {
throw new Error('Failed to get screenshots from Automate.' +
' Check dashboard for error.');
}

const tiles = [];
const tileResponse = JSON.parse(responseValue.result);

for (let tileData of tileResponse.sha) {
tiles.push(new Tile({
statusBarHeight: 0,
navBarHeight: 0,
headerHeight: 0,
footerHeight: 0,
fullscreen,
sha: tileData.split('-')[0] // drop build id
// dom-sha: tileData.dom_sha
}));
}
return tiles;
}

async browserstackExecutor(action, args) {
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
let options = args ? { action, arguments: args } : { action };
Expand Down
33 changes: 25 additions & 8 deletions packages/webdriver-utils/src/providers/genericProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import Tile from '../util/tile.js';
import Driver from '../driver.js';

const log = utils.logger('webdriver-utils:genericProvider');
// TODO: Need to pass parameter from sdk and catch in cli
const CLIENT_INFO = 'local-poc-poa';
const ENV_INFO = 'staging-poc-poa';

export default class GenericProvider {
clientInfo = new Set();
environmentInfo = new Set();
options = {};
constructor(
sessionId,
commandExecutorUrl,
capabilities,
sessionCapabilites
sessionCapabilites,
clientInfo,
environmentInfo,
options
) {
this.sessionId = sessionId;
this.commandExecutorUrl = commandExecutorUrl;
this.capabilities = capabilities;
this.sessionCapabilites = sessionCapabilites;
this.addClientInfo(clientInfo);
this.addEnvironmentInfo(environmentInfo);
this.options = options;
this.driver = null;
this.metaData = null;
this.debugUrl = null;
Expand All @@ -35,6 +41,18 @@ export default class GenericProvider {
return true;
}

addClientInfo(info) {
for (let i of [].concat(info)) {
if (i) this.clientInfo.add(i);
}
}

addEnvironmentInfo(info) {
for (let i of [].concat(info)) {
if (i) this.environmentInfo.add(i);
}
}

async screenshot(name) {
let fullscreen = false;

Expand All @@ -51,8 +69,8 @@ export default class GenericProvider {
tiles,
// TODO: Fetch this one for bs automate, check appium sdk
externalDebugUrl: this.debugUrl,
environmentInfo: ENV_INFO,
clientInfo: CLIENT_INFO
environmentInfo: [...this.environmentInfo].join('; '),
clientInfo: [...this.clientInfo].join(' ')
};
}

Expand Down Expand Up @@ -84,8 +102,7 @@ export default class GenericProvider {
height,
orientation: orientation,
browserName: this.metaData.browserName(),
// TODO
browserVersion: 'unknown'
browserVersion: this.metaData.browserVersion()
};
}

Expand Down
4 changes: 2 additions & 2 deletions packages/webdriver-utils/src/providers/providerResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import GenericProvider from './genericProvider.js';
import AutomateProvider from './automateProvider.js';

export default class ProviderResolver {
static resolve(sessionId, commandExecutorUrl, capabilities, sessionCapabilities) {
static resolve(sessionId, commandExecutorUrl, capabilities, sessionCapabilities, clientInfo, environmentInfo, options) {
// We can safely do [0] because GenericProvider is catch all
const Klass = [AutomateProvider, GenericProvider].filter(x => x.supports(commandExecutorUrl))[0];
return new Klass(sessionId, commandExecutorUrl, capabilities, sessionCapabilities);
return new Klass(sessionId, commandExecutorUrl, capabilities, sessionCapabilities, clientInfo, environmentInfo, options);
}
}
54 changes: 54 additions & 0 deletions packages/webdriver-utils/src/util/timing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import validations from './validations.js';
const { Undefined } = validations;

export default class TimeIt {
static data = {};

static enabled = process.env.PERCY_METRICS === 'true';

static async run(store, func) {
if (!this.enabled) return await func();

const t1 = Date.now();
try {
return await func();
} finally {
if (Undefined(this.data[store])) this.data[store] = [];
this.data[store].push(Date.now() - t1);
}
}

static min(store) {
return Math.min(...this.data[store]);
}

static max(store) {
return Math.max(...this.data[store]);
}

static avg(store) {
const vals = this.data[store];

return vals.reduce((a, b) => a + b, 0) / vals.length;
}

static summary({
includeVals
} = {}) {
const agg = {};
for (const key of Object.keys(this.data)) {
agg[key] = {
min: this.min(key),
max: this.max(key),
avg: this.avg(key),
count: this.data[key].length
};
if (includeVals) agg[key].vals = this.data[key];
}
return agg;
}

static reset() {
this.data = {};
}
};

0 comments on commit 4dd8743

Please sign in to comment.