@percy/webdriver-utils 1.27.0-beta.0 → 1.27.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/driver.js +16 -4
- package/dist/index.js +11 -6
- package/dist/metadata/desktopMetaData.js +27 -15
- package/dist/metadata/metaDataResolver.js +2 -1
- package/dist/metadata/mobileMetaData.js +31 -17
- package/dist/metadata/normalizeData.js +34 -0
- package/dist/providers/automateProvider.js +70 -20
- package/dist/providers/genericProvider.js +57 -38
- package/dist/util/cache.js +2 -0
- package/package.json +4 -4
- package/dist/util/timing.js +0 -47
- package/dist/util/validations.js +0 -7
package/dist/driver.js
CHANGED
|
@@ -3,16 +3,23 @@ import Cache from './util/cache.js';
|
|
|
3
3
|
const {
|
|
4
4
|
request
|
|
5
5
|
} = utils;
|
|
6
|
+
const log = utils.logger('webdriver-utils:driver');
|
|
6
7
|
export default class Driver {
|
|
7
|
-
constructor(sessionId, executorUrl) {
|
|
8
|
+
constructor(sessionId, executorUrl, passedCapabilities) {
|
|
8
9
|
this.sessionId = sessionId;
|
|
9
10
|
this.executorUrl = executorUrl.includes('@') ? `https://${executorUrl.split('@')[1]}` : executorUrl;
|
|
11
|
+
this.passedCapabilities = passedCapabilities;
|
|
10
12
|
}
|
|
11
13
|
async getCapabilites() {
|
|
12
14
|
return await Cache.withCache(Cache.caps, this.sessionId, async () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
const baseUrl = `${this.executorUrl}/session/${this.sessionId}`;
|
|
17
|
+
const caps = JSON.parse((await request(baseUrl)).body);
|
|
18
|
+
return caps.value;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
log.warn(`Falling back to legacy protocol, Error: ${err.message}`);
|
|
21
|
+
return this.passedCapabilities;
|
|
22
|
+
}
|
|
16
23
|
});
|
|
17
24
|
}
|
|
18
25
|
async getWindowSize() {
|
|
@@ -26,6 +33,11 @@ export default class Driver {
|
|
|
26
33
|
if (!command.constructor === Object || !(Object.keys(command).length === 2 && Object.keys(command).includes('script') && Object.keys(command).includes('args'))) {
|
|
27
34
|
throw new Error('Please pass command as {script: "", args: []}');
|
|
28
35
|
}
|
|
36
|
+
// browser_executor is custom BS executor script, if there is anything extra it breaks
|
|
37
|
+
// percy_automate_script is an anchor comment to identify percy automate scripts
|
|
38
|
+
if (!command.script.includes('browserstack_executor')) {
|
|
39
|
+
command.script = `/* percy_automate_script */ \n ${command.script}`;
|
|
40
|
+
}
|
|
29
41
|
const options = {
|
|
30
42
|
method: 'POST',
|
|
31
43
|
headers: {
|
package/dist/index.js
CHANGED
|
@@ -30,11 +30,16 @@ export default class WebdriverUtils {
|
|
|
30
30
|
this.buildInfo = buildInfo;
|
|
31
31
|
}
|
|
32
32
|
async automateScreenshot() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
try {
|
|
34
|
+
this.log.info(`[${this.snapshotName}] : Starting automate screenshot ...`);
|
|
35
|
+
const automate = ProviderResolver.resolve(this.sessionId, this.commandExecutorUrl, this.capabilities, this.sessionCapabilites, this.clientInfo, this.environmentInfo, this.options, this.buildInfo);
|
|
36
|
+
this.log.debug(`[${this.snapshotName}] : Resolved provider ...`);
|
|
37
|
+
await automate.createDriver();
|
|
38
|
+
this.log.debug(`[${this.snapshotName}] : Created driver ...`);
|
|
39
|
+
return await automate.screenshot(this.snapshotName, this.options);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
this.log.error(`[${this.snapshotName}] : Error - ${e.message}`);
|
|
42
|
+
this.log.error(`[${this.snapshotName}] : Error Log - ${e.toString()}`);
|
|
43
|
+
}
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
+
import Cache from '../util/cache.js';
|
|
1
2
|
export default class DesktopMetaData {
|
|
2
3
|
constructor(driver, opts) {
|
|
3
4
|
this.driver = driver;
|
|
4
5
|
this.capabilities = opts;
|
|
5
6
|
}
|
|
7
|
+
device() {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
6
10
|
browserName() {
|
|
7
|
-
|
|
11
|
+
var _this$capabilities, _this$capabilities$br;
|
|
12
|
+
return (_this$capabilities = this.capabilities) === null || _this$capabilities === void 0 ? void 0 : (_this$capabilities$br = _this$capabilities.browserName) === null || _this$capabilities$br === void 0 ? void 0 : _this$capabilities$br.toLowerCase();
|
|
8
13
|
}
|
|
9
14
|
browserVersion() {
|
|
10
|
-
|
|
15
|
+
var _this$capabilities2, _this$capabilities2$b;
|
|
16
|
+
return (_this$capabilities2 = this.capabilities) === null || _this$capabilities2 === void 0 ? void 0 : (_this$capabilities2$b = _this$capabilities2.browserVersion) === null || _this$capabilities2$b === void 0 ? void 0 : _this$capabilities2$b.split('.')[0];
|
|
11
17
|
}
|
|
12
18
|
osName() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
osName =
|
|
19
|
+
var _this$capabilities3, _osName, _this$capabilities4;
|
|
20
|
+
let osName = (_this$capabilities3 = this.capabilities) === null || _this$capabilities3 === void 0 ? void 0 : _this$capabilities3.os;
|
|
21
|
+
if (osName) return (_osName = osName) === null || _osName === void 0 ? void 0 : _osName.toLowerCase();
|
|
22
|
+
osName = (_this$capabilities4 = this.capabilities) === null || _this$capabilities4 === void 0 ? void 0 : _this$capabilities4.platform;
|
|
16
23
|
return osName;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
// showing major version
|
|
20
27
|
osVersion() {
|
|
21
|
-
|
|
28
|
+
var _this$capabilities5, _this$capabilities5$o;
|
|
29
|
+
return (_this$capabilities5 = this.capabilities) === null || _this$capabilities5 === void 0 ? void 0 : (_this$capabilities5$o = _this$capabilities5.osVersion) === null || _this$capabilities5$o === void 0 ? void 0 : _this$capabilities5$o.toLowerCase();
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
// combination of browserName + browserVersion + osVersion + osName
|
|
@@ -39,18 +47,22 @@ export default class DesktopMetaData {
|
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
async screenResolution() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
return await Cache.withCache(Cache.resolution, this.driver.sessionId, async () => {
|
|
51
|
+
const data = await this.driver.executeScript({
|
|
52
|
+
script: 'return [(window.screen.width * window.devicePixelRatio).toString(), (window.screen.height * window.devicePixelRatio).toString()];',
|
|
53
|
+
args: []
|
|
54
|
+
});
|
|
55
|
+
const screenInfo = data.value;
|
|
56
|
+
return `${screenInfo[0]} x ${screenInfo[1]}`;
|
|
45
57
|
});
|
|
46
|
-
const screenInfo = data.value;
|
|
47
|
-
return `${screenInfo[0]} x ${screenInfo[1]}`;
|
|
48
58
|
}
|
|
49
59
|
async devicePixelRatio() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
return await Cache.withCache(Cache.dpr, this.driver.sessionId, async () => {
|
|
61
|
+
const devicePixelRatio = await this.driver.executeScript({
|
|
62
|
+
script: 'return window.devicePixelRatio;',
|
|
63
|
+
args: []
|
|
64
|
+
});
|
|
65
|
+
return devicePixelRatio.value;
|
|
53
66
|
});
|
|
54
|
-
return devicePixelRatio.value;
|
|
55
67
|
}
|
|
56
68
|
}
|
|
@@ -2,9 +2,10 @@ import DesktopMetaData from './desktopMetaData.js';
|
|
|
2
2
|
import MobileMetaData from './mobileMetaData.js';
|
|
3
3
|
export default class MetaDataResolver {
|
|
4
4
|
static resolve(driver, capabilities, opts) {
|
|
5
|
+
var _capabilities$platfor, _capabilities$device, _capabilities$device$;
|
|
5
6
|
if (!driver) throw new Error('Please pass a Driver object');
|
|
6
7
|
const platform = opts.platformName || opts.platform;
|
|
7
|
-
if (['ios', 'android'].includes(platform.toLowerCase())) {
|
|
8
|
+
if (['ios', 'android'].includes(platform.toLowerCase()) || ['ios', 'android'].includes(capabilities === null || capabilities === void 0 ? void 0 : (_capabilities$platfor = capabilities.platformName) === null || _capabilities$platfor === void 0 ? void 0 : _capabilities$platfor.toLowerCase()) || ['ipad', 'iphone'].includes(capabilities === null || capabilities === void 0 ? void 0 : (_capabilities$device = capabilities.device) === null || _capabilities$device === void 0 ? void 0 : (_capabilities$device$ = _capabilities$device.toString()) === null || _capabilities$device$ === void 0 ? void 0 : _capabilities$device$.toLowerCase())) {
|
|
8
9
|
return new MobileMetaData(driver, capabilities);
|
|
9
10
|
} else {
|
|
10
11
|
return new DesktopMetaData(driver, capabilities);
|
|
@@ -1,34 +1,44 @@
|
|
|
1
|
+
import Cache from '../util/cache.js';
|
|
2
|
+
// Todo: Implement a base metadata for the common functions.
|
|
1
3
|
export default class MobileMetaData {
|
|
2
4
|
constructor(driver, opts) {
|
|
3
5
|
this.driver = driver;
|
|
4
6
|
this.capabilities = opts;
|
|
5
7
|
}
|
|
8
|
+
device() {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
6
11
|
browserName() {
|
|
7
|
-
|
|
12
|
+
var _this$capabilities, _this$capabilities$br;
|
|
13
|
+
return (_this$capabilities = this.capabilities) === null || _this$capabilities === void 0 ? void 0 : (_this$capabilities$br = _this$capabilities.browserName) === null || _this$capabilities$br === void 0 ? void 0 : _this$capabilities$br.toLowerCase();
|
|
8
14
|
}
|
|
9
15
|
browserVersion() {
|
|
10
|
-
var _this$capabilities$
|
|
11
|
-
const bsVersion = (_this$capabilities$
|
|
16
|
+
var _this$capabilities$br2, _this$capabilities2, _this$capabilities2$v;
|
|
17
|
+
const bsVersion = (_this$capabilities$br2 = this.capabilities.browserVersion) === null || _this$capabilities$br2 === void 0 ? void 0 : _this$capabilities$br2.split('.');
|
|
12
18
|
if ((bsVersion === null || bsVersion === void 0 ? void 0 : bsVersion.length) > 0) {
|
|
13
19
|
return bsVersion[0];
|
|
14
20
|
}
|
|
15
|
-
return this.capabilities.version.split('.')[0];
|
|
21
|
+
return (_this$capabilities2 = this.capabilities) === null || _this$capabilities2 === void 0 ? void 0 : (_this$capabilities2$v = _this$capabilities2.version) === null || _this$capabilities2$v === void 0 ? void 0 : _this$capabilities2$v.split('.')[0];
|
|
16
22
|
}
|
|
17
23
|
osName() {
|
|
18
|
-
|
|
24
|
+
var _this$capabilities3, _this$capabilities3$o;
|
|
25
|
+
let osName = (_this$capabilities3 = this.capabilities) === null || _this$capabilities3 === void 0 ? void 0 : (_this$capabilities3$o = _this$capabilities3.os) === null || _this$capabilities3$o === void 0 ? void 0 : _this$capabilities3$o.toLowerCase();
|
|
19
26
|
if (osName === 'mac' && this.browserName() === 'iphone') {
|
|
20
27
|
osName = 'ios';
|
|
21
28
|
}
|
|
22
29
|
return osName;
|
|
23
30
|
}
|
|
24
31
|
osVersion() {
|
|
25
|
-
|
|
32
|
+
var _this$capabilities4, _this$capabilities4$o;
|
|
33
|
+
return (_this$capabilities4 = this.capabilities) === null || _this$capabilities4 === void 0 ? void 0 : (_this$capabilities4$o = _this$capabilities4.osVersion) === null || _this$capabilities4$o === void 0 ? void 0 : _this$capabilities4$o.split('.')[0];
|
|
26
34
|
}
|
|
27
35
|
deviceName() {
|
|
28
|
-
|
|
36
|
+
var _this$capabilities5, _this$capabilities5$d;
|
|
37
|
+
return (_this$capabilities5 = this.capabilities) === null || _this$capabilities5 === void 0 ? void 0 : (_this$capabilities5$d = _this$capabilities5.deviceName) === null || _this$capabilities5$d === void 0 ? void 0 : _this$capabilities5$d.split('-')[0];
|
|
29
38
|
}
|
|
30
39
|
orientation() {
|
|
31
|
-
|
|
40
|
+
var _this$capabilities6;
|
|
41
|
+
return (_this$capabilities6 = this.capabilities) === null || _this$capabilities6 === void 0 ? void 0 : _this$capabilities6.orientation;
|
|
32
42
|
}
|
|
33
43
|
async windowSize() {
|
|
34
44
|
const dpr = await this.devicePixelRatio();
|
|
@@ -41,18 +51,22 @@ export default class MobileMetaData {
|
|
|
41
51
|
};
|
|
42
52
|
}
|
|
43
53
|
async screenResolution() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
return await Cache.withCache(Cache.resolution, this.driver.sessionId, async () => {
|
|
55
|
+
const data = await this.driver.executeScript({
|
|
56
|
+
script: 'return [parseInt(window.screen.width * window.devicePixelRatio).toString(), parseInt(window.screen.height * window.devicePixelRatio).toString()];',
|
|
57
|
+
args: []
|
|
58
|
+
});
|
|
59
|
+
const screenInfo = data.value;
|
|
60
|
+
return `${screenInfo[0]} x ${screenInfo[1]}`;
|
|
47
61
|
});
|
|
48
|
-
const screenInfo = data.value;
|
|
49
|
-
return `${screenInfo[0]} x ${screenInfo[1]}`;
|
|
50
62
|
}
|
|
51
63
|
async devicePixelRatio() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
return await Cache.withCache(Cache.dpr, this.driver.sessionId, async () => {
|
|
65
|
+
const devicePixelRatio = await this.driver.executeScript({
|
|
66
|
+
script: 'return window.devicePixelRatio;',
|
|
67
|
+
args: []
|
|
68
|
+
});
|
|
69
|
+
return devicePixelRatio.value;
|
|
55
70
|
});
|
|
56
|
-
return devicePixelRatio.value;
|
|
57
71
|
}
|
|
58
72
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default class NormalizeData {
|
|
2
|
+
osRollUp(os) {
|
|
3
|
+
if (os.toLowerCase().startsWith('win')) {
|
|
4
|
+
return 'Windows';
|
|
5
|
+
} else if (os.toLowerCase().startsWith('mac')) {
|
|
6
|
+
return 'OS X';
|
|
7
|
+
} else if (os.toLowerCase().includes('iphone') || os.toLowerCase().startsWith('ios')) {
|
|
8
|
+
return 'iOS';
|
|
9
|
+
} else if (os.toLowerCase().startsWith('android')) {
|
|
10
|
+
return 'Android';
|
|
11
|
+
}
|
|
12
|
+
return os;
|
|
13
|
+
}
|
|
14
|
+
browserRollUp(browserName, device) {
|
|
15
|
+
if (device) {
|
|
16
|
+
if (browserName !== null && browserName !== void 0 && browserName.toLowerCase().includes('chrome')) {
|
|
17
|
+
return 'chrome';
|
|
18
|
+
} else if (browserName !== null && browserName !== void 0 && browserName.toLowerCase().includes('iphone') || browserName !== null && browserName !== void 0 && browserName.toLowerCase().includes('ipad')) {
|
|
19
|
+
return 'safari';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return browserName;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Responses for browser version differ for devices and desktops from capabilities
|
|
26
|
+
// Differenences in selenium and appium responses causes inconsistency
|
|
27
|
+
// So to tackle for devices on UI we will show device names else browser versions
|
|
28
|
+
browserVersionOrDeviceNameRollup(browserVersion, deviceName, device) {
|
|
29
|
+
if (device) {
|
|
30
|
+
return deviceName;
|
|
31
|
+
}
|
|
32
|
+
return browserVersion === null || browserVersion === void 0 ? void 0 : browserVersion.split('.')[0];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -2,6 +2,7 @@ import utils from '@percy/sdk-utils';
|
|
|
2
2
|
import GenericProvider from './genericProvider.js';
|
|
3
3
|
import Cache from '../util/cache.js';
|
|
4
4
|
import Tile from '../util/tile.js';
|
|
5
|
+
import NormalizeData from '../metadata/normalizeData.js';
|
|
5
6
|
const log = utils.logger('webdriver-utils:automateProvider');
|
|
6
7
|
const {
|
|
7
8
|
TimeIt
|
|
@@ -10,6 +11,7 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
10
11
|
constructor(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo) {
|
|
11
12
|
super(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo);
|
|
12
13
|
this._markedPercy = false;
|
|
14
|
+
this.automateResults = null;
|
|
13
15
|
}
|
|
14
16
|
static supports(commandExecutorUrl) {
|
|
15
17
|
return commandExecutorUrl.includes(process.env.AA_DOMAIN || 'browserstack');
|
|
@@ -18,21 +20,30 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
18
20
|
ignoreRegionXpaths = [],
|
|
19
21
|
ignoreRegionSelectors = [],
|
|
20
22
|
ignoreRegionElements = [],
|
|
21
|
-
customIgnoreRegions = []
|
|
23
|
+
customIgnoreRegions = [],
|
|
24
|
+
considerRegionXpaths = [],
|
|
25
|
+
considerRegionSelectors = [],
|
|
26
|
+
considerRegionElements = [],
|
|
27
|
+
customConsiderRegions = []
|
|
22
28
|
}) {
|
|
23
29
|
let response = null;
|
|
24
30
|
let error;
|
|
25
|
-
log.
|
|
31
|
+
log.debug(`[${name}] : Preparing to capture screenshots on automate ...`);
|
|
26
32
|
try {
|
|
27
|
-
log.debug(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
log.debug(`[${name}] : Marking automate session as percy ...`);
|
|
34
|
+
const result = await this.percyScreenshotBegin(name);
|
|
35
|
+
this.automateResults = JSON.parse(result.value);
|
|
36
|
+
log.debug(`[${name}] : Fetching the debug url ...`);
|
|
37
|
+
this.setDebugUrl();
|
|
31
38
|
response = await super.screenshot(name, {
|
|
32
39
|
ignoreRegionXpaths,
|
|
33
40
|
ignoreRegionSelectors,
|
|
34
41
|
ignoreRegionElements,
|
|
35
|
-
customIgnoreRegions
|
|
42
|
+
customIgnoreRegions,
|
|
43
|
+
considerRegionXpaths,
|
|
44
|
+
considerRegionSelectors,
|
|
45
|
+
considerRegionElements,
|
|
46
|
+
customConsiderRegions
|
|
36
47
|
});
|
|
37
48
|
} catch (e) {
|
|
38
49
|
error = e;
|
|
@@ -55,8 +66,8 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
55
66
|
this._markedPercy = result.success;
|
|
56
67
|
return result;
|
|
57
68
|
} catch (e) {
|
|
58
|
-
log.debug(`[${name}] Could not mark Automate session as percy`);
|
|
59
|
-
log.error(`[${name}] error: ${e.toString()}`);
|
|
69
|
+
log.debug(`[${name}] : Could not mark Automate session as percy`);
|
|
70
|
+
log.error(`[${name}] : error: ${e.toString()}`);
|
|
60
71
|
return null;
|
|
61
72
|
}
|
|
62
73
|
});
|
|
@@ -72,23 +83,22 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
72
83
|
state: 'end'
|
|
73
84
|
});
|
|
74
85
|
} catch (e) {
|
|
75
|
-
log.debug(`[${name}] Could not execute percyScreenshot command for Automate`);
|
|
86
|
+
log.debug(`[${name}] : Could not execute percyScreenshot command for Automate`);
|
|
76
87
|
log.error(e);
|
|
77
88
|
}
|
|
78
89
|
});
|
|
79
90
|
}
|
|
80
91
|
async getTiles(headerHeight, footerHeight, fullscreen) {
|
|
92
|
+
var _responseValue$metada;
|
|
81
93
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
82
|
-
log.
|
|
94
|
+
log.debug('Starting actual screenshotting phase');
|
|
95
|
+
const dpr = await this.metaData.devicePixelRatio();
|
|
83
96
|
const response = await TimeIt.run('percyScreenshot:screenshot', async () => {
|
|
84
97
|
return await this.browserstackExecutor('percyScreenshot', {
|
|
85
98
|
state: 'screenshot',
|
|
86
99
|
percyBuildId: this.buildInfo.id,
|
|
87
100
|
screenshotType: 'singlepage',
|
|
88
|
-
scaleFactor:
|
|
89
|
-
script: 'return window.devicePixelRatio;',
|
|
90
|
-
args: []
|
|
91
|
-
}),
|
|
101
|
+
scaleFactor: dpr,
|
|
92
102
|
options: this.options
|
|
93
103
|
});
|
|
94
104
|
});
|
|
@@ -99,10 +109,11 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
99
109
|
const tiles = [];
|
|
100
110
|
const tileResponse = JSON.parse(responseValue.result);
|
|
101
111
|
log.debug('Tiles captured successfully');
|
|
112
|
+
const windowHeight = (responseValue === null || responseValue === void 0 ? void 0 : (_responseValue$metada = responseValue.metadata) === null || _responseValue$metada === void 0 ? void 0 : _responseValue$metada.window_height) || 0;
|
|
102
113
|
for (let tileData of tileResponse.sha) {
|
|
103
114
|
tiles.push(new Tile({
|
|
104
|
-
statusBarHeight: 0,
|
|
105
|
-
navBarHeight: 0,
|
|
115
|
+
statusBarHeight: tileResponse.header_height || 0,
|
|
116
|
+
navBarHeight: tileResponse.footer_height || 0,
|
|
106
117
|
headerHeight,
|
|
107
118
|
footerHeight,
|
|
108
119
|
fullscreen,
|
|
@@ -110,9 +121,13 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
110
121
|
}));
|
|
111
122
|
}
|
|
112
123
|
|
|
124
|
+
const metadata = {
|
|
125
|
+
windowHeight: Math.ceil(windowHeight * dpr)
|
|
126
|
+
};
|
|
113
127
|
return {
|
|
114
128
|
tiles: tiles,
|
|
115
|
-
domInfoSha: tileResponse.dom_sha
|
|
129
|
+
domInfoSha: tileResponse.dom_sha,
|
|
130
|
+
metadata: metadata
|
|
116
131
|
};
|
|
117
132
|
}
|
|
118
133
|
async browserstackExecutor(action, args) {
|
|
@@ -132,8 +147,43 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
132
147
|
async setDebugUrl() {
|
|
133
148
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
134
149
|
this.debugUrl = await Cache.withCache(Cache.bstackSessionDetails, this.driver.sessionId, async () => {
|
|
135
|
-
|
|
136
|
-
return JSON.parse(sessionDetails.value).browser_url;
|
|
150
|
+
return `https://automate.browserstack.com/builds/${this.automateResults.buildHash}/sessions/${this.automateResults.sessionHash}`;
|
|
137
151
|
});
|
|
138
152
|
}
|
|
153
|
+
async getTag() {
|
|
154
|
+
var _automateCaps$os_vers, _ref;
|
|
155
|
+
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
156
|
+
if (!this.automateResults) throw new Error('Comparison tag details not available');
|
|
157
|
+
const automateCaps = this.automateResults.capabilities;
|
|
158
|
+
const normalizeTags = new NormalizeData();
|
|
159
|
+
let deviceName = this.automateResults.deviceName;
|
|
160
|
+
const osName = normalizeTags.osRollUp(automateCaps.os);
|
|
161
|
+
const osVersion = (_automateCaps$os_vers = automateCaps.os_version) === null || _automateCaps$os_vers === void 0 ? void 0 : _automateCaps$os_vers.split('.')[0];
|
|
162
|
+
const browserName = normalizeTags.browserRollUp(automateCaps.browserName, this.metaData.device());
|
|
163
|
+
const browserVersion = normalizeTags.browserVersionOrDeviceNameRollup(automateCaps.browserVersion, deviceName, this.metaData.device());
|
|
164
|
+
if (!this.metaData.device()) {
|
|
165
|
+
deviceName = `${osName}_${osVersion}_${browserName}_${browserVersion}`;
|
|
166
|
+
}
|
|
167
|
+
let {
|
|
168
|
+
width,
|
|
169
|
+
height
|
|
170
|
+
} = await this.metaData.windowSize();
|
|
171
|
+
const resolution = await this.metaData.screenResolution();
|
|
172
|
+
const orientation = (_ref = this.metaData.orientation() || automateCaps.deviceOrientation) === null || _ref === void 0 ? void 0 : _ref.toLowerCase();
|
|
173
|
+
|
|
174
|
+
// for android window size only constitutes of browser viewport, hence adding nav / status / url bar heights
|
|
175
|
+
[this.header, this.footer] = await this.getHeaderFooter(deviceName, osVersion, browserName);
|
|
176
|
+
height = this.metaData.device() && (osName === null || osName === void 0 ? void 0 : osName.toLowerCase()) === 'android' ? height + this.header + this.footer : height;
|
|
177
|
+
return {
|
|
178
|
+
name: deviceName,
|
|
179
|
+
osName,
|
|
180
|
+
osVersion,
|
|
181
|
+
width,
|
|
182
|
+
height,
|
|
183
|
+
orientation,
|
|
184
|
+
browserName,
|
|
185
|
+
browserVersion,
|
|
186
|
+
resolution
|
|
187
|
+
};
|
|
188
|
+
}
|
|
139
189
|
}
|
|
@@ -56,7 +56,7 @@ export default class GenericProvider {
|
|
|
56
56
|
}`;
|
|
57
57
|
}
|
|
58
58
|
async createDriver() {
|
|
59
|
-
this.driver = new Driver(this.sessionId, this.commandExecutorUrl);
|
|
59
|
+
this.driver = new Driver(this.sessionId, this.commandExecutorUrl, this.capabilities);
|
|
60
60
|
log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`);
|
|
61
61
|
const caps = await this.driver.getCapabilites();
|
|
62
62
|
log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`);
|
|
@@ -97,21 +97,26 @@ export default class GenericProvider {
|
|
|
97
97
|
ignoreRegionXpaths = [],
|
|
98
98
|
ignoreRegionSelectors = [],
|
|
99
99
|
ignoreRegionElements = [],
|
|
100
|
-
customIgnoreRegions = []
|
|
100
|
+
customIgnoreRegions = [],
|
|
101
|
+
considerRegionXpaths = [],
|
|
102
|
+
considerRegionSelectors = [],
|
|
103
|
+
considerRegionElements = [],
|
|
104
|
+
customConsiderRegions = []
|
|
101
105
|
}) {
|
|
102
106
|
let fullscreen = false;
|
|
103
107
|
this.addDefaultOptions();
|
|
104
108
|
const percyCSS = (this.defaultPercyCSS() + (this.options.percyCSS || '')).split('\n').join('');
|
|
105
|
-
log.debug(`Applying the percyCSS - ${this.options.percyCSS}`);
|
|
109
|
+
log.debug(`[${name}] : Applying the percyCSS - ${this.options.percyCSS}`);
|
|
106
110
|
await this.addPercyCSS(percyCSS);
|
|
107
111
|
log.debug('Fetching comparisong tag ...');
|
|
108
112
|
const tag = await this.getTag();
|
|
109
|
-
log.debug(
|
|
113
|
+
log.debug(`[${name}] : Tag ${JSON.stringify(tag)}`);
|
|
110
114
|
const tiles = await this.getTiles(this.header, this.footer, fullscreen);
|
|
111
|
-
log.debug(
|
|
112
|
-
const ignoreRegions = await this.
|
|
115
|
+
log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`);
|
|
116
|
+
const ignoreRegions = await this.findRegions(ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions);
|
|
117
|
+
const considerRegions = await this.findRegions(considerRegionXpaths, considerRegionSelectors, considerRegionElements, customConsiderRegions);
|
|
113
118
|
await this.setDebugUrl();
|
|
114
|
-
log.debug(
|
|
119
|
+
log.debug(`[${name}] : Debug url ${this.debugUrl}`);
|
|
115
120
|
await this.removePercyCSS();
|
|
116
121
|
return {
|
|
117
122
|
name,
|
|
@@ -119,10 +124,16 @@ export default class GenericProvider {
|
|
|
119
124
|
tiles: tiles.tiles,
|
|
120
125
|
// TODO: Fetch this one for bs automate, check appium sdk
|
|
121
126
|
externalDebugUrl: this.debugUrl,
|
|
122
|
-
ignoredElementsData:
|
|
127
|
+
ignoredElementsData: {
|
|
128
|
+
ignoreElementsData: ignoreRegions
|
|
129
|
+
},
|
|
130
|
+
consideredElementsData: {
|
|
131
|
+
considerElementsData: considerRegions
|
|
132
|
+
},
|
|
123
133
|
environmentInfo: [...this.environmentInfo].join('; '),
|
|
124
134
|
clientInfo: [...this.clientInfo].join(' '),
|
|
125
|
-
domInfoSha: tiles.domInfoSha
|
|
135
|
+
domInfoSha: tiles.domInfoSha,
|
|
136
|
+
metadata: tiles.metadata || null
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
139
|
|
|
@@ -131,6 +142,14 @@ export default class GenericProvider {
|
|
|
131
142
|
// execute script and return dom content
|
|
132
143
|
return 'dummyValue';
|
|
133
144
|
}
|
|
145
|
+
async getWindowHeight() {
|
|
146
|
+
// execute script and return window height
|
|
147
|
+
return await this.driver.executeScript({
|
|
148
|
+
script: 'return window.innerHeight',
|
|
149
|
+
args: []
|
|
150
|
+
});
|
|
151
|
+
;
|
|
152
|
+
}
|
|
134
153
|
async getTiles(headerHeight, footerHeight, fullscreen) {
|
|
135
154
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
136
155
|
const base64content = await this.driver.takeScreenshot();
|
|
@@ -145,7 +164,10 @@ export default class GenericProvider {
|
|
|
145
164
|
fullscreen
|
|
146
165
|
})],
|
|
147
166
|
// TODO: Add Generic support sha for contextual diff for non-automate
|
|
148
|
-
domInfoSha: await this.getDomContent()
|
|
167
|
+
domInfoSha: await this.getDomContent(),
|
|
168
|
+
metadata: {
|
|
169
|
+
windowHeight: await this.getWindowHeight()
|
|
170
|
+
}
|
|
149
171
|
};
|
|
150
172
|
}
|
|
151
173
|
async getTag() {
|
|
@@ -176,16 +198,14 @@ export default class GenericProvider {
|
|
|
176
198
|
async setDebugUrl() {
|
|
177
199
|
this.debugUrl = 'https://localhost/v1';
|
|
178
200
|
}
|
|
179
|
-
async
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
return
|
|
185
|
-
ignoreElementsData: [...ignoreElementXpaths, ...ignoreElementSelectors, ...ignoreElements, ...ignoreElementCustom]
|
|
186
|
-
};
|
|
201
|
+
async findRegions(xpaths, selectors, elements, customLocations) {
|
|
202
|
+
const xpathRegions = await this.getSeleniumRegionsBy('xpath', xpaths);
|
|
203
|
+
const selectorRegions = await this.getSeleniumRegionsBy('css selector', selectors);
|
|
204
|
+
const elementRegions = await this.getSeleniumRegionsByElement(elements);
|
|
205
|
+
const customRegions = await this.getSeleniumRegionsByLocation(customLocations);
|
|
206
|
+
return [...xpathRegions, ...selectorRegions, ...elementRegions, ...customRegions];
|
|
187
207
|
}
|
|
188
|
-
async
|
|
208
|
+
async getRegionObject(selector, elementId) {
|
|
189
209
|
const scaleFactor = parseInt(await this.metaData.devicePixelRatio());
|
|
190
210
|
const rect = await this.driver.rect(elementId);
|
|
191
211
|
const location = {
|
|
@@ -208,37 +228,37 @@ export default class GenericProvider {
|
|
|
208
228
|
};
|
|
209
229
|
return jsonObject;
|
|
210
230
|
}
|
|
211
|
-
async
|
|
212
|
-
const
|
|
231
|
+
async getSeleniumRegionsBy(findBy, elements) {
|
|
232
|
+
const regionsArray = [];
|
|
213
233
|
for (const idx in elements) {
|
|
214
234
|
try {
|
|
215
235
|
const element = await this.driver.findElement(findBy, elements[idx]);
|
|
216
236
|
const selector = `${findBy}: ${elements[idx]}`;
|
|
217
|
-
const
|
|
218
|
-
|
|
237
|
+
const region = await this.getRegionObject(selector, element[Object.keys(element)[0]]);
|
|
238
|
+
regionsArray.push(region);
|
|
219
239
|
} catch (e) {
|
|
220
240
|
log.warn(`Selenium Element with ${findBy}: ${elements[idx]} not found. Ignoring this ${findBy}.`);
|
|
221
241
|
log.error(e.toString());
|
|
222
242
|
}
|
|
223
243
|
}
|
|
224
|
-
return
|
|
244
|
+
return regionsArray;
|
|
225
245
|
}
|
|
226
|
-
async
|
|
227
|
-
const
|
|
246
|
+
async getSeleniumRegionsByElement(elements) {
|
|
247
|
+
const regionsArray = [];
|
|
228
248
|
for (let index = 0; index < elements.length; index++) {
|
|
229
249
|
try {
|
|
230
250
|
const selector = `element: ${index}`;
|
|
231
|
-
const
|
|
232
|
-
|
|
251
|
+
const region = await this.getRegionObject(selector, elements[index]);
|
|
252
|
+
regionsArray.push(region);
|
|
233
253
|
} catch (e) {
|
|
234
254
|
log.warn(`Correct Web Element not passed at index ${index}.`);
|
|
235
255
|
log.debug(e.toString());
|
|
236
256
|
}
|
|
237
257
|
}
|
|
238
|
-
return
|
|
258
|
+
return regionsArray;
|
|
239
259
|
}
|
|
240
|
-
async
|
|
241
|
-
const
|
|
260
|
+
async getSeleniumRegionsByLocation(customLocations) {
|
|
261
|
+
const elementsArray = [];
|
|
242
262
|
const {
|
|
243
263
|
width,
|
|
244
264
|
height
|
|
@@ -247,8 +267,8 @@ export default class GenericProvider {
|
|
|
247
267
|
const customLocation = customLocations[index];
|
|
248
268
|
const invalid = customLocation.top >= height || customLocation.bottom > height || customLocation.left >= width || customLocation.right > width;
|
|
249
269
|
if (!invalid) {
|
|
250
|
-
const selector = `custom
|
|
251
|
-
const
|
|
270
|
+
const selector = `custom region ${index}`;
|
|
271
|
+
const region = {
|
|
252
272
|
selector,
|
|
253
273
|
coOrdinates: {
|
|
254
274
|
top: customLocation.top,
|
|
@@ -257,20 +277,19 @@ export default class GenericProvider {
|
|
|
257
277
|
right: customLocation.right
|
|
258
278
|
}
|
|
259
279
|
};
|
|
260
|
-
|
|
280
|
+
elementsArray.push(region);
|
|
261
281
|
} else {
|
|
262
282
|
log.warn(`Values passed in custom ignored region at index: ${index} is not valid`);
|
|
263
283
|
}
|
|
264
284
|
}
|
|
265
|
-
return
|
|
285
|
+
return elementsArray;
|
|
266
286
|
}
|
|
267
|
-
async getHeaderFooter() {
|
|
287
|
+
async getHeaderFooter(deviceName, osVersion, browserName) {
|
|
268
288
|
// passing 0 as key, since across different pages and tests, this config will remain same
|
|
269
289
|
const devicesConfig = await Cache.withCache(Cache.devicesConfig, 0, async () => {
|
|
270
290
|
return (await request(DEVICES_CONFIG_URL)).body;
|
|
271
291
|
});
|
|
272
|
-
let deviceKey = `${
|
|
273
|
-
let browserName = this.capabilities.browserName;
|
|
292
|
+
let deviceKey = `${deviceName}-${osVersion}`;
|
|
274
293
|
return devicesConfig[deviceKey] ? devicesConfig[deviceKey][browserName] ? [devicesConfig[deviceKey][browserName].header, devicesConfig[deviceKey][browserName].footer] : [0, 0] : [0, 0];
|
|
275
294
|
}
|
|
276
295
|
}
|
package/dist/util/cache.js
CHANGED
|
@@ -10,6 +10,8 @@ export default class Cache {
|
|
|
10
10
|
static bstackSessionDetails = 'bstackSessionDetails';
|
|
11
11
|
static systemBars = 'systemBars';
|
|
12
12
|
static devicesConfig = 'devicesConfig';
|
|
13
|
+
static dpr = 'dpr';
|
|
14
|
+
static resolution = 'resolution';
|
|
13
15
|
|
|
14
16
|
// maintainance
|
|
15
17
|
static lastTime = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/webdriver-utils",
|
|
3
|
-
"version": "1.27.0-beta.
|
|
3
|
+
"version": "1.27.0-beta.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"test:coverage": "yarn test --coverage"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@percy/config": "1.27.0-beta.
|
|
33
|
-
"@percy/sdk-utils": "1.27.0-beta.
|
|
32
|
+
"@percy/config": "1.27.0-beta.2",
|
|
33
|
+
"@percy/sdk-utils": "1.27.0-beta.2"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "bac46d4a3352884d4466cbc543ea599af33536ca"
|
|
36
36
|
}
|
package/dist/util/timing.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import validations from './validations.js';
|
|
2
|
-
const {
|
|
3
|
-
Undefined
|
|
4
|
-
} = validations;
|
|
5
|
-
export default class TimeIt {
|
|
6
|
-
static data = {};
|
|
7
|
-
static enabled = process.env.PERCY_METRICS === 'true';
|
|
8
|
-
static async run(store, func) {
|
|
9
|
-
if (!this.enabled) return await func();
|
|
10
|
-
const t1 = Date.now();
|
|
11
|
-
try {
|
|
12
|
-
return await func();
|
|
13
|
-
} finally {
|
|
14
|
-
if (Undefined(this.data[store])) this.data[store] = [];
|
|
15
|
-
this.data[store].push(Date.now() - t1);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
static min(store) {
|
|
19
|
-
return Math.min(...this.data[store]);
|
|
20
|
-
}
|
|
21
|
-
static max(store) {
|
|
22
|
-
return Math.max(...this.data[store]);
|
|
23
|
-
}
|
|
24
|
-
static avg(store) {
|
|
25
|
-
const vals = this.data[store];
|
|
26
|
-
return vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
27
|
-
}
|
|
28
|
-
static summary({
|
|
29
|
-
includeVals
|
|
30
|
-
} = {}) {
|
|
31
|
-
const agg = {};
|
|
32
|
-
for (const key of Object.keys(this.data)) {
|
|
33
|
-
agg[key] = {
|
|
34
|
-
min: this.min(key),
|
|
35
|
-
max: this.max(key),
|
|
36
|
-
avg: this.avg(key),
|
|
37
|
-
count: this.data[key].length
|
|
38
|
-
};
|
|
39
|
-
if (includeVals) agg[key].vals = this.data[key];
|
|
40
|
-
}
|
|
41
|
-
return agg;
|
|
42
|
-
}
|
|
43
|
-
static reset() {
|
|
44
|
-
this.data = {};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
;
|