Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for legacy protocol #1323

Merged
merged 2 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions packages/webdriver-utils/src/driver.js
Original file line number Diff line number Diff line change
@@ -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;
}
});
}

Expand Down
6 changes: 3 additions & 3 deletions packages/webdriver-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
32 changes: 21 additions & 11 deletions packages/webdriver-utils/src/metadata/desktopMetaData.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
});
}
}
4 changes: 3 additions & 1 deletion packages/webdriver-utils/src/metadata/metaDataResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 21 additions & 11 deletions packages/webdriver-utils/src/metadata/mobileMetaData.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
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() {
const bsVersion = this.capabilities.browserVersion?.split('.');
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';
}
return osName;
}

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() {
Expand All @@ -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;
});
}
}
34 changes: 34 additions & 0 deletions packages/webdriver-utils/src/metadata/normalizeData.js
Original file line number Diff line number Diff line change
@@ -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];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didnt understand why are we using deviceName for browserVersionRollUp ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason being that in case of devices we don't really get the browserVersion properly. In capabilities we are getting the browserVersion as the iphone or device_name itself in certain devices. Responses differ when using different appium and selenium Version. And from the UI perspective to keep it consistent we moved it to keep device_name this ensures no UI change is required and nothing is breaking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, then can we just rename function to browserVersionOrDeviceNameRollup and add the comment on top ? [ the same comment as what you have explained above

}
64 changes: 52 additions & 12 deletions packages/webdriver-utils/src/providers/automateProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +29,7 @@ export default class AutomateProvider extends GenericProvider {
buildInfo
);
this._markedPercy = false;
this.automateResults = null;
}

static supports(commandExecutorUrl) {
Expand All @@ -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;
Expand All @@ -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;
}
});
Expand All @@ -88,22 +91,22 @@ 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);
}
});
}

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
});
});
Expand Down Expand Up @@ -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
};
}
}
Loading
Loading