@percy/webdriver-utils 1.28.8-beta.1 → 1.28.8-beta.3
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/index.js +19 -8
- package/dist/playwrightDriver.js +34 -0
- package/dist/providers/automateProvider.js +18 -92
- package/dist/providers/genericProvider.js +133 -47
- package/dist/providers/playwrightProvider.js +136 -0
- package/dist/providers/providerResolver.js +12 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
import ProviderResolver from './providers/providerResolver.js';
|
|
2
2
|
import utils from '@percy/sdk-utils';
|
|
3
|
+
import PlaywrightProvider from './providers/playwrightProvider.js';
|
|
3
4
|
export default class WebdriverUtils {
|
|
4
|
-
static async
|
|
5
|
+
static async captureScreenshot({
|
|
5
6
|
sessionId,
|
|
6
7
|
commandExecutorUrl,
|
|
7
8
|
capabilities,
|
|
8
|
-
|
|
9
|
+
sessionCapabilities,
|
|
10
|
+
frameGuid,
|
|
11
|
+
pageGuid,
|
|
12
|
+
framework,
|
|
9
13
|
snapshotName,
|
|
10
14
|
clientInfo,
|
|
11
15
|
environmentInfo,
|
|
12
16
|
options = {},
|
|
13
17
|
buildInfo = {}
|
|
14
18
|
}) {
|
|
15
|
-
const log = utils.logger('webdriver-utils:
|
|
19
|
+
const log = utils.logger('webdriver-utils:captureScreenshot');
|
|
16
20
|
try {
|
|
17
21
|
const startTime = Date.now();
|
|
18
|
-
log.info(`[${snapshotName}] : Starting automate screenshot ...`);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
log.info(`[${snapshotName}] : Starting automate screenshot capture ...`);
|
|
23
|
+
let provider;
|
|
24
|
+
switch (framework ? framework.toLowerCase() : null) {
|
|
25
|
+
case 'playwright':
|
|
26
|
+
provider = new PlaywrightProvider(sessionId, frameGuid, pageGuid, clientInfo, environmentInfo, options, buildInfo);
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
provider = ProviderResolver.resolve(sessionId, commandExecutorUrl, capabilities, sessionCapabilities, clientInfo, environmentInfo, options, buildInfo);
|
|
30
|
+
}
|
|
31
|
+
await provider.createDriver();
|
|
22
32
|
log.debug(`[${snapshotName}] : Created driver ...`);
|
|
23
|
-
const comparisonData = await
|
|
33
|
+
const comparisonData = await provider.screenshot(snapshotName, options);
|
|
24
34
|
comparisonData.metadata.cliScreenshotStartTime = startTime;
|
|
25
35
|
comparisonData.metadata.cliScreenshotEndTime = Date.now();
|
|
26
36
|
comparisonData.sync = options.sync;
|
|
@@ -31,6 +41,7 @@ export default class WebdriverUtils {
|
|
|
31
41
|
} catch (e) {
|
|
32
42
|
log.error(`[${snapshotName}] : Error - ${e.message}`);
|
|
33
43
|
log.error(`[${snapshotName}] : Error Log - ${e.toString()}`);
|
|
44
|
+
throw e; // Re-throw the error to maintain consistency in error handling
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import utils from '@percy/sdk-utils';
|
|
2
|
+
const {
|
|
3
|
+
request
|
|
4
|
+
} = utils;
|
|
5
|
+
export default class PlaywrightDriver {
|
|
6
|
+
constructor(sessionId) {
|
|
7
|
+
this.sessionId = sessionId;
|
|
8
|
+
}
|
|
9
|
+
requestPostOptions(command) {
|
|
10
|
+
return {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: {
|
|
13
|
+
'Content-Type': 'application/json;charset=utf-8'
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify(command)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// command => {script: "", args: []}
|
|
20
|
+
async executeScript(command) {
|
|
21
|
+
if (!command.constructor === Object || !(Object.keys(command).length === 2 && Object.keys(command).includes('script') && Object.keys(command).includes('args'))) {
|
|
22
|
+
throw new Error('Please pass command as {script: "", args: []}');
|
|
23
|
+
}
|
|
24
|
+
// browser_executor is custom BS executor script, if there is anything extra it breaks
|
|
25
|
+
// percy_automate_script is an anchor comment to identify percy automate scripts
|
|
26
|
+
if (!command.script.includes('browserstack_executor')) {
|
|
27
|
+
command.script = `/* percy_automate_script */ \n ${command.script}`;
|
|
28
|
+
}
|
|
29
|
+
const options = this.requestPostOptions(command);
|
|
30
|
+
const baseUrl = `https://cdp.browserstack.com/wd/hub/session/${this.sessionId}/execute`;
|
|
31
|
+
const response = JSON.parse((await request(baseUrl, options)).body);
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -2,18 +2,21 @@ 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';
|
|
6
5
|
import TimeIt from '../util/timing.js';
|
|
6
|
+
import MetaDataResolver from '../metadata/metaDataResolver.js';
|
|
7
|
+
import Driver from '../driver.js';
|
|
7
8
|
const log = utils.logger('webdriver-utils:automateProvider');
|
|
8
9
|
export default class AutomateProvider extends GenericProvider {
|
|
9
|
-
constructor(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo) {
|
|
10
|
-
super(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo);
|
|
11
|
-
this._markedPercy = false;
|
|
12
|
-
this.automateResults = null;
|
|
13
|
-
}
|
|
14
10
|
static supports(commandExecutorUrl) {
|
|
15
11
|
return commandExecutorUrl.includes(process.env.AA_DOMAIN || 'browserstack');
|
|
16
12
|
}
|
|
13
|
+
async createDriver() {
|
|
14
|
+
this.driver = new Driver(this.sessionId, this.commandExecutorUrl, this.capabilities);
|
|
15
|
+
log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`);
|
|
16
|
+
const caps = await this.driver.getCapabilites();
|
|
17
|
+
log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`);
|
|
18
|
+
this.metaData = MetaDataResolver.resolve(this.driver, caps, this.capabilities);
|
|
19
|
+
}
|
|
17
20
|
async screenshot(name, {
|
|
18
21
|
ignoreRegionXpaths = [],
|
|
19
22
|
ignoreRegionSelectors = [],
|
|
@@ -51,62 +54,12 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
51
54
|
}
|
|
52
55
|
return response;
|
|
53
56
|
}
|
|
54
|
-
async percyScreenshotBegin(name) {
|
|
55
|
-
return await TimeIt.run('percyScreenshotBegin', async () => {
|
|
56
|
-
try {
|
|
57
|
-
let result = await this.browserstackExecutor('percyScreenshot', {
|
|
58
|
-
name,
|
|
59
|
-
percyBuildId: this.buildInfo.id,
|
|
60
|
-
percyBuildUrl: this.buildInfo.url,
|
|
61
|
-
state: 'begin'
|
|
62
|
-
});
|
|
63
|
-
// Selenium Hub, set status error Code to 13 if an error is thrown
|
|
64
|
-
// Handling error with Selenium dialect is != W3C
|
|
65
|
-
if ((result === null || result === void 0 ? void 0 : result.status) === 13) throw new Error((result === null || result === void 0 ? void 0 : result.value) || 'Got invalid error response');
|
|
66
|
-
this._markedPercy = result.success;
|
|
67
|
-
return result;
|
|
68
|
-
} catch (e) {
|
|
69
|
-
var _e$response, _JSON$parse, _e$response2;
|
|
70
|
-
log.debug(`[${name}] : Could not mark Automate session as percy`);
|
|
71
|
-
log.error(`[${name}] : error: ${e.toString()}`);
|
|
72
|
-
/**
|
|
73
|
-
* - Handling Error when dialect is W3C
|
|
74
|
-
* ERROR response format from SeleniumHUB `{
|
|
75
|
-
* sessionId: ...,
|
|
76
|
-
* status: 13,
|
|
77
|
-
* value: { error: '', message: ''}
|
|
78
|
-
* }
|
|
79
|
-
*/
|
|
80
|
-
const errResponse = (e === null || e === void 0 ? void 0 : (_e$response = e.response) === null || _e$response === void 0 ? void 0 : _e$response.body) && ((_JSON$parse = JSON.parse(e === null || e === void 0 ? void 0 : (_e$response2 = e.response) === null || _e$response2 === void 0 ? void 0 : _e$response2.body)) === null || _JSON$parse === void 0 ? void 0 : _JSON$parse.value) || {};
|
|
81
|
-
const errMessage = (errResponse === null || errResponse === void 0 ? void 0 : errResponse.message) || (errResponse === null || errResponse === void 0 ? void 0 : errResponse.error) || (e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : e.error) || (e === null || e === void 0 ? void 0 : e.value) || e.toString();
|
|
82
|
-
throw new Error(errMessage);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
async percyScreenshotEnd(name, error) {
|
|
87
|
-
return await TimeIt.run('percyScreenshotEnd', async () => {
|
|
88
|
-
try {
|
|
89
|
-
var _this$buildInfo, _this$options;
|
|
90
|
-
await this.browserstackExecutor('percyScreenshot', {
|
|
91
|
-
name,
|
|
92
|
-
percyScreenshotUrl: (_this$buildInfo = this.buildInfo) === null || _this$buildInfo === void 0 ? void 0 : _this$buildInfo.url,
|
|
93
|
-
status: error ? 'failure' : 'success',
|
|
94
|
-
statusMessage: error ? `${error}` : '',
|
|
95
|
-
state: 'end',
|
|
96
|
-
sync: (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.sync
|
|
97
|
-
});
|
|
98
|
-
} catch (e) {
|
|
99
|
-
log.debug(`[${name}] : Could not execute percyScreenshot command for Automate`);
|
|
100
|
-
log.error(e);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
57
|
async getTiles(fullscreen) {
|
|
105
|
-
var _this$
|
|
58
|
+
var _this$options;
|
|
106
59
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
107
60
|
log.debug('Starting actual screenshotting phase');
|
|
108
61
|
const dpr = await this.metaData.devicePixelRatio();
|
|
109
|
-
const screenshotType = (_this$
|
|
62
|
+
const screenshotType = (_this$options = this.options) !== null && _this$options !== void 0 && _this$options.fullPage ? 'fullpage' : 'singlepage';
|
|
110
63
|
const response = await TimeIt.run('percyScreenshot:screenshot', async () => {
|
|
111
64
|
return await this.browserstackExecutor('percyScreenshot', {
|
|
112
65
|
state: 'screenshot',
|
|
@@ -143,20 +96,6 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
143
96
|
metadata: metadata
|
|
144
97
|
};
|
|
145
98
|
}
|
|
146
|
-
async browserstackExecutor(action, args) {
|
|
147
|
-
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
148
|
-
let options = args ? {
|
|
149
|
-
action,
|
|
150
|
-
arguments: args
|
|
151
|
-
} : {
|
|
152
|
-
action
|
|
153
|
-
};
|
|
154
|
-
let res = await this.driver.executeScript({
|
|
155
|
-
script: `browserstack_executor: ${JSON.stringify(options)}`,
|
|
156
|
-
args: []
|
|
157
|
-
});
|
|
158
|
-
return res;
|
|
159
|
-
}
|
|
160
99
|
async setDebugUrl() {
|
|
161
100
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
162
101
|
this.debugUrl = await Cache.withCache(Cache.bstackSessionDetails, this.driver.sessionId, async () => {
|
|
@@ -164,35 +103,22 @@ export default class AutomateProvider extends GenericProvider {
|
|
|
164
103
|
});
|
|
165
104
|
}
|
|
166
105
|
async getTag() {
|
|
167
|
-
var
|
|
106
|
+
var _this$metaData$orient;
|
|
168
107
|
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
169
|
-
if (!this.automateResults) throw new Error('Comparison tag details not available');
|
|
170
|
-
const automateCaps = this.automateResults.capabilities;
|
|
171
|
-
const normalizeTags = new NormalizeData();
|
|
172
|
-
let deviceName = this.automateResults.deviceName;
|
|
173
|
-
const osName = normalizeTags.osRollUp(automateCaps.os);
|
|
174
|
-
const osVersion = (_automateCaps$os_vers = automateCaps.os_version) === null || _automateCaps$os_vers === void 0 ? void 0 : _automateCaps$os_vers.split('.')[0];
|
|
175
|
-
const browserName = normalizeTags.browserRollUp(automateCaps.browserName, this.metaData.device());
|
|
176
|
-
const browserVersion = normalizeTags.browserVersionOrDeviceNameRollup(automateCaps.browserVersion, deviceName, this.metaData.device());
|
|
177
|
-
if (!this.metaData.device()) {
|
|
178
|
-
deviceName = `${osName}_${osVersion}_${browserName}_${browserVersion}`;
|
|
179
|
-
}
|
|
180
108
|
let {
|
|
181
109
|
width,
|
|
182
110
|
height
|
|
183
111
|
} = await this.metaData.windowSize();
|
|
184
112
|
const resolution = await this.metaData.screenResolution();
|
|
185
|
-
const orientation = (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
osName,
|
|
189
|
-
osVersion,
|
|
113
|
+
const orientation = (_this$metaData$orient = this.metaData.orientation()) === null || _this$metaData$orient === void 0 ? void 0 : _this$metaData$orient.toLowerCase();
|
|
114
|
+
const device = this.metaData.device();
|
|
115
|
+
const tagData = {
|
|
190
116
|
width,
|
|
191
117
|
height,
|
|
118
|
+
resolution,
|
|
192
119
|
orientation,
|
|
193
|
-
|
|
194
|
-
browserVersion,
|
|
195
|
-
resolution
|
|
120
|
+
device
|
|
196
121
|
};
|
|
122
|
+
return await super.getTag(tagData);
|
|
197
123
|
}
|
|
198
124
|
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import utils from '@percy/sdk-utils';
|
|
2
|
-
import
|
|
2
|
+
import TimeIt from '../util/timing.js';
|
|
3
3
|
import Tile from '../util/tile.js';
|
|
4
4
|
import Driver from '../driver.js';
|
|
5
|
+
import MetaDataResolver from '../metadata/metaDataResolver.js';
|
|
6
|
+
import NormalizeData from '../metadata/normalizeData.js';
|
|
5
7
|
const log = utils.logger('webdriver-utils:genericProvider');
|
|
6
8
|
export default class GenericProvider {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
this.sessionCapabilites = sessionCapabilites;
|
|
15
|
-
this.addClientInfo(clientInfo);
|
|
16
|
-
this.addEnvironmentInfo(environmentInfo);
|
|
17
|
-
this.options = options;
|
|
18
|
-
this.buildInfo = buildInfo;
|
|
19
|
-
this.driver = null;
|
|
9
|
+
clientInfoDetails = new Set();
|
|
10
|
+
environmentInfoDetails = new Set();
|
|
11
|
+
constructor(args) {
|
|
12
|
+
Object.assign(this, args);
|
|
13
|
+
this.addClientInfo(this.clientInfo);
|
|
14
|
+
this.addEnvironmentInfo(this.environmentInfo);
|
|
15
|
+
this._markedPercy = false;
|
|
20
16
|
this.metaData = null;
|
|
21
17
|
this.debugUrl = null;
|
|
18
|
+
this.driver = null;
|
|
22
19
|
this.header = 0;
|
|
23
20
|
this.footer = 0;
|
|
24
21
|
this.statusBarHeight = 0;
|
|
@@ -36,19 +33,19 @@ export default class GenericProvider {
|
|
|
36
33
|
log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`);
|
|
37
34
|
const caps = await this.driver.getCapabilites();
|
|
38
35
|
log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`);
|
|
39
|
-
this.metaData =
|
|
36
|
+
this.metaData = MetaDataResolver.resolve(this.driver, caps, this.capabilities);
|
|
40
37
|
}
|
|
41
38
|
static supports(_commandExecutorUrl) {
|
|
42
39
|
return true;
|
|
43
40
|
}
|
|
44
41
|
addClientInfo(info) {
|
|
45
42
|
for (let i of [].concat(info)) {
|
|
46
|
-
if (i) this.
|
|
43
|
+
if (i) this.clientInfoDetails.add(i);
|
|
47
44
|
}
|
|
48
45
|
}
|
|
49
46
|
addEnvironmentInfo(info) {
|
|
50
47
|
for (let i of [].concat(info)) {
|
|
51
|
-
if (i) this.
|
|
48
|
+
if (i) this.environmentInfoDetails.add(i);
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
51
|
isIOS() {
|
|
@@ -74,6 +71,72 @@ export default class GenericProvider {
|
|
|
74
71
|
args: []
|
|
75
72
|
});
|
|
76
73
|
}
|
|
74
|
+
async browserstackExecutor(action, args) {
|
|
75
|
+
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
76
|
+
let options = args ? {
|
|
77
|
+
action,
|
|
78
|
+
arguments: args
|
|
79
|
+
} : {
|
|
80
|
+
action
|
|
81
|
+
};
|
|
82
|
+
let res = await this.driver.executeScript({
|
|
83
|
+
script: `browserstack_executor: ${JSON.stringify(options)}`,
|
|
84
|
+
args: []
|
|
85
|
+
});
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
async percyScreenshotBegin(name) {
|
|
89
|
+
return await TimeIt.run('percyScreenshotBegin', async () => {
|
|
90
|
+
try {
|
|
91
|
+
let result = await this.browserstackExecutor('percyScreenshot', {
|
|
92
|
+
name,
|
|
93
|
+
percyBuildId: this.buildInfo.id,
|
|
94
|
+
percyBuildUrl: this.buildInfo.url,
|
|
95
|
+
state: 'begin'
|
|
96
|
+
});
|
|
97
|
+
// Selenium Hub, set status error Code to 13 if an error is thrown
|
|
98
|
+
// Handling error with Selenium dialect is != W3C
|
|
99
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 13) {
|
|
100
|
+
throw new Error((result === null || result === void 0 ? void 0 : result.value) || 'Got invalid error response');
|
|
101
|
+
}
|
|
102
|
+
this._markedPercy = result.success;
|
|
103
|
+
return result;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
var _e$response, _JSON$parse, _e$response2;
|
|
106
|
+
log.debug(`[${name}] : Could not mark Automate session as percy`);
|
|
107
|
+
log.error(`[${name}] : error: ${e.toString()}`);
|
|
108
|
+
/**
|
|
109
|
+
* - Handling Error when dialect is W3C
|
|
110
|
+
* ERROR response format from SeleniumHUB `{
|
|
111
|
+
* sessionId: ...,
|
|
112
|
+
* status: 13,
|
|
113
|
+
* value: { error: '', message: ''}
|
|
114
|
+
* }
|
|
115
|
+
*/
|
|
116
|
+
const errResponse = (e === null || e === void 0 ? void 0 : (_e$response = e.response) === null || _e$response === void 0 ? void 0 : _e$response.body) && ((_JSON$parse = JSON.parse(e === null || e === void 0 ? void 0 : (_e$response2 = e.response) === null || _e$response2 === void 0 ? void 0 : _e$response2.body)) === null || _JSON$parse === void 0 ? void 0 : _JSON$parse.value) || {};
|
|
117
|
+
const errMessage = (errResponse === null || errResponse === void 0 ? void 0 : errResponse.message) || (errResponse === null || errResponse === void 0 ? void 0 : errResponse.error) || (e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : e.error) || (e === null || e === void 0 ? void 0 : e.value) || e.toString();
|
|
118
|
+
throw new Error(errMessage);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async percyScreenshotEnd(name, error) {
|
|
123
|
+
return await TimeIt.run('percyScreenshotEnd', async () => {
|
|
124
|
+
try {
|
|
125
|
+
var _this$buildInfo, _this$options;
|
|
126
|
+
await this.browserstackExecutor('percyScreenshot', {
|
|
127
|
+
name,
|
|
128
|
+
percyScreenshotUrl: (_this$buildInfo = this.buildInfo) === null || _this$buildInfo === void 0 ? void 0 : _this$buildInfo.url,
|
|
129
|
+
status: error ? 'failure' : 'success',
|
|
130
|
+
statusMessage: error ? `${error}` : '',
|
|
131
|
+
state: 'end',
|
|
132
|
+
sync: (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.sync
|
|
133
|
+
});
|
|
134
|
+
} catch (e) {
|
|
135
|
+
log.debug(`[${name}] : Could not execute percyScreenshot command for Automate`);
|
|
136
|
+
log.error(e);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
77
140
|
async screenshot(name, {
|
|
78
141
|
ignoreRegionXpaths = [],
|
|
79
142
|
ignoreRegionSelectors = [],
|
|
@@ -110,12 +173,21 @@ export default class GenericProvider {
|
|
|
110
173
|
consideredElementsData: {
|
|
111
174
|
considerElementsData: considerRegions
|
|
112
175
|
},
|
|
113
|
-
environmentInfo:
|
|
114
|
-
clientInfo:
|
|
176
|
+
environmentInfo: this.getUserAgentString(this.environmentInfoDetails),
|
|
177
|
+
clientInfo: this.getUserAgentString(this.clientInfoDetails),
|
|
115
178
|
domInfoSha: tiles.domInfoSha,
|
|
116
179
|
metadata: tiles.metadata || null
|
|
117
180
|
};
|
|
118
181
|
}
|
|
182
|
+
getUserAgentString(data) {
|
|
183
|
+
let result = '';
|
|
184
|
+
if (data instanceof Set) {
|
|
185
|
+
result = [...data].join('; ');
|
|
186
|
+
} else if (typeof data === 'string') {
|
|
187
|
+
result = data;
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
119
191
|
|
|
120
192
|
// TODO: get dom sha for non-automate
|
|
121
193
|
async getDomContent() {
|
|
@@ -150,33 +222,13 @@ export default class GenericProvider {
|
|
|
150
222
|
}
|
|
151
223
|
};
|
|
152
224
|
}
|
|
153
|
-
async getTag() {
|
|
154
|
-
if (!this.driver) throw new Error('Driver is null, please initialize driver with createDriver().');
|
|
155
|
-
let {
|
|
156
|
-
width,
|
|
157
|
-
height
|
|
158
|
-
} = await this.metaData.windowSize();
|
|
159
|
-
const resolution = await this.metaData.screenResolution();
|
|
160
|
-
const orientation = this.metaData.orientation();
|
|
161
|
-
return {
|
|
162
|
-
name: this.metaData.deviceName(),
|
|
163
|
-
osName: this.metaData.osName(),
|
|
164
|
-
osVersion: this.metaData.osVersion(),
|
|
165
|
-
width,
|
|
166
|
-
height,
|
|
167
|
-
orientation: orientation,
|
|
168
|
-
browserName: this.metaData.browserName(),
|
|
169
|
-
browserVersion: this.metaData.browserVersion(),
|
|
170
|
-
resolution: resolution
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
225
|
|
|
174
226
|
// TODO: Add Debugging Url for non-automate
|
|
175
227
|
async setDebugUrl() {
|
|
176
228
|
this.debugUrl = 'https://localhost/v1';
|
|
177
229
|
}
|
|
178
230
|
async doTransformations() {
|
|
179
|
-
var _this$
|
|
231
|
+
var _this$options2;
|
|
180
232
|
const hideScrollbarStyle = `
|
|
181
233
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
|
182
234
|
::-webkit-scrollbar {
|
|
@@ -197,7 +249,7 @@ export default class GenericProvider {
|
|
|
197
249
|
script: jsScript,
|
|
198
250
|
args: []
|
|
199
251
|
});
|
|
200
|
-
if ((_this$
|
|
252
|
+
if ((_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.fullPage || this.isIOS()) {
|
|
201
253
|
await this.getInitialScrollLocation();
|
|
202
254
|
}
|
|
203
255
|
}
|
|
@@ -225,11 +277,11 @@ export default class GenericProvider {
|
|
|
225
277
|
}
|
|
226
278
|
}
|
|
227
279
|
async getRegionObjectFromBoundingBox(selector, element) {
|
|
228
|
-
var _this$
|
|
280
|
+
var _this$options3;
|
|
229
281
|
const scaleFactor = await this.metaData.devicePixelRatio();
|
|
230
282
|
let scrollX = 0,
|
|
231
283
|
scrollY = 0;
|
|
232
|
-
if ((_this$
|
|
284
|
+
if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.fullPage) {
|
|
233
285
|
scrollX = this.initialScrollLocation.value[0];
|
|
234
286
|
scrollY = this.initialScrollLocation.value[1];
|
|
235
287
|
}
|
|
@@ -265,14 +317,14 @@ export default class GenericProvider {
|
|
|
265
317
|
return regionsArray;
|
|
266
318
|
}
|
|
267
319
|
async updatePageShiftFactor(location, scaleFactor, scrollFactors) {
|
|
268
|
-
var _this$
|
|
320
|
+
var _this$options4;
|
|
269
321
|
if (this.isIOS() || this.currentTag.osName === 'OS X' && parseInt(this.currentTag.browserVersion) > 13 && this.currentTag.browserName.toLowerCase() === 'safari') {
|
|
270
322
|
this.pageYShiftFactor = this.statusBarHeight;
|
|
271
323
|
} else {
|
|
272
324
|
this.pageYShiftFactor = this.statusBarHeight - scrollFactors.value[1] * scaleFactor;
|
|
273
325
|
}
|
|
274
326
|
this.pageXShiftFactor = this.isIOS() ? 0 : -(scrollFactors.value[0] * scaleFactor);
|
|
275
|
-
if (this.isIOS() && !((_this$
|
|
327
|
+
if (this.isIOS() && !((_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.fullPage)) {
|
|
276
328
|
if (scrollFactors.value[0] !== this.initialScrollLocation.value[0] || scrollFactors.value[1] !== this.initialScrollLocation.value[1]) {
|
|
277
329
|
this.pageXShiftFactor = -1 * this.removeElementShiftFactor;
|
|
278
330
|
this.pageYShiftFactor = -1 * this.removeElementShiftFactor;
|
|
@@ -282,7 +334,7 @@ export default class GenericProvider {
|
|
|
282
334
|
}
|
|
283
335
|
}
|
|
284
336
|
async getRegionObject(selector, elementId) {
|
|
285
|
-
var _this$
|
|
337
|
+
var _this$options5;
|
|
286
338
|
const scaleFactor = await this.metaData.devicePixelRatio();
|
|
287
339
|
const rect = await this.driver.rect(elementId);
|
|
288
340
|
const location = {
|
|
@@ -296,7 +348,7 @@ export default class GenericProvider {
|
|
|
296
348
|
let scrollX = 0,
|
|
297
349
|
scrollY = 0;
|
|
298
350
|
const scrollFactors = await this.getScrollDetails();
|
|
299
|
-
if ((_this$
|
|
351
|
+
if ((_this$options5 = this.options) !== null && _this$options5 !== void 0 && _this$options5.fullPage) {
|
|
300
352
|
scrollX = scrollFactors.value[0];
|
|
301
353
|
scrollY = scrollFactors.value[1];
|
|
302
354
|
}
|
|
@@ -362,4 +414,38 @@ export default class GenericProvider {
|
|
|
362
414
|
}
|
|
363
415
|
return elementsArray;
|
|
364
416
|
}
|
|
417
|
+
async getTag(tagData) {
|
|
418
|
+
var _automateCaps$os_vers;
|
|
419
|
+
if (!this.automateResults) throw new Error('Comparison tag details not available');
|
|
420
|
+
const automateCaps = this.automateResults.capabilities;
|
|
421
|
+
const normalizeTags = new NormalizeData();
|
|
422
|
+
let deviceName = this.automateResults.deviceName;
|
|
423
|
+
const osName = normalizeTags.osRollUp(automateCaps.os);
|
|
424
|
+
const osVersion = (_automateCaps$os_vers = automateCaps.os_version) === null || _automateCaps$os_vers === void 0 ? void 0 : _automateCaps$os_vers.split('.')[0];
|
|
425
|
+
const browserName = normalizeTags.browserRollUp(automateCaps.browserName, tagData.device);
|
|
426
|
+
const browserVersion = normalizeTags.browserVersionOrDeviceNameRollup(automateCaps.browserVersion, deviceName, tagData.device);
|
|
427
|
+
if (!tagData.device) {
|
|
428
|
+
deviceName = `${osName}_${osVersion}_${browserName}_${browserVersion}`;
|
|
429
|
+
}
|
|
430
|
+
let {
|
|
431
|
+
width,
|
|
432
|
+
height
|
|
433
|
+
} = {
|
|
434
|
+
width: tagData.width,
|
|
435
|
+
height: tagData.height
|
|
436
|
+
};
|
|
437
|
+
const resolution = tagData.resolution;
|
|
438
|
+
const orientation = tagData.orientation || automateCaps.deviceOrientation || 'landscape';
|
|
439
|
+
return {
|
|
440
|
+
name: deviceName,
|
|
441
|
+
osName,
|
|
442
|
+
osVersion,
|
|
443
|
+
width,
|
|
444
|
+
height,
|
|
445
|
+
orientation,
|
|
446
|
+
browserName,
|
|
447
|
+
browserVersion,
|
|
448
|
+
resolution
|
|
449
|
+
};
|
|
450
|
+
}
|
|
365
451
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import utils from '@percy/sdk-utils';
|
|
2
|
+
import TimeIt from '../util/timing.js';
|
|
3
|
+
import NormalizeData from '../metadata/normalizeData.js';
|
|
4
|
+
import Tile from '../util/tile.js';
|
|
5
|
+
import GenericProvider from './genericProvider.js';
|
|
6
|
+
import PlaywrightDriver from '../playwrightDriver.js';
|
|
7
|
+
const log = utils.logger('webdriver-utils:genericProvider');
|
|
8
|
+
export default class PlaywrightProvider extends GenericProvider {
|
|
9
|
+
constructor(sessionId, frameGuid, pageGuid, clientInfo, environmentInfo, options, buildInfo) {
|
|
10
|
+
super({
|
|
11
|
+
sessionId,
|
|
12
|
+
frameGuid,
|
|
13
|
+
pageGuid,
|
|
14
|
+
clientInfo,
|
|
15
|
+
environmentInfo,
|
|
16
|
+
options,
|
|
17
|
+
buildInfo
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async createDriver() {
|
|
21
|
+
this.driver = new PlaywrightDriver(this.sessionId);
|
|
22
|
+
}
|
|
23
|
+
async setDebugUrl() {
|
|
24
|
+
this.debugUrl = `https://automate.browserstack.com/builds/${this.automateResults.buildHash}/sessions/${this.automateResults.sessionHash}`;
|
|
25
|
+
}
|
|
26
|
+
async screenshot(name, options) {
|
|
27
|
+
let response = null;
|
|
28
|
+
let error;
|
|
29
|
+
log.debug(`[${name}] : Preparing to capture screenshots on playwrght with automate ...`);
|
|
30
|
+
try {
|
|
31
|
+
log.debug(`[${name}] : Marking automate session as percy ...`);
|
|
32
|
+
const result = await this.percyScreenshotBegin(name);
|
|
33
|
+
this.automateResults = JSON.parse(result.value);
|
|
34
|
+
log.debug(`[${name}] : Begin response ${this.automateResults}`);
|
|
35
|
+
log.debug(`[${name}] : Fetching the debug url ...`);
|
|
36
|
+
this.setDebugUrl();
|
|
37
|
+
const tiles = await this.getTiles();
|
|
38
|
+
log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`);
|
|
39
|
+
log.debug('Fetching comparisong tag ...');
|
|
40
|
+
const tag = await this.getTag(tiles.tagData);
|
|
41
|
+
log.debug(`[${name}] : Tag ${JSON.stringify(tag)}`);
|
|
42
|
+
response = {
|
|
43
|
+
name,
|
|
44
|
+
tag,
|
|
45
|
+
tiles: tiles.tiles,
|
|
46
|
+
// TODO: Fetch this one for bs automate, check appium sdk
|
|
47
|
+
externalDebugUrl: this.debugUrl,
|
|
48
|
+
ignoredElementsData: {
|
|
49
|
+
ignoreElementsData: tiles.ignoreRegionsData
|
|
50
|
+
},
|
|
51
|
+
consideredElementsData: {
|
|
52
|
+
considerElementsData: tiles.considerRegionsData
|
|
53
|
+
},
|
|
54
|
+
environmentInfo: this.environmentInfo,
|
|
55
|
+
clientInfo: this.clientInfo,
|
|
56
|
+
domInfoSha: tiles.domInfoSha,
|
|
57
|
+
metadata: tiles.metadata || null
|
|
58
|
+
};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.trace(e);
|
|
61
|
+
error = e;
|
|
62
|
+
throw e;
|
|
63
|
+
} finally {
|
|
64
|
+
await this.percyScreenshotEnd(name, error);
|
|
65
|
+
}
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
async getTiles(fullscreen = true) {
|
|
69
|
+
var _this$options;
|
|
70
|
+
log.debug(`Starting actual screenshotting phase with Page GUID: ${this.pageGuid}, Frame GUID: ${this.frameGuid}`);
|
|
71
|
+
const screenshotType = (_this$options = this.options) !== null && _this$options !== void 0 && _this$options.fullPage ? 'fullpage' : 'singlepage';
|
|
72
|
+
const response = await TimeIt.run('percyScreenshot:screenshot', async () => {
|
|
73
|
+
return await this.browserstackExecutor('percyScreenshot', {
|
|
74
|
+
state: 'screenshot',
|
|
75
|
+
percyBuildId: this.buildInfo.id,
|
|
76
|
+
screenshotType: screenshotType,
|
|
77
|
+
scaleFactor: 1,
|
|
78
|
+
options: this.options,
|
|
79
|
+
frameworkData: {
|
|
80
|
+
frameGuid: this.frameGuid,
|
|
81
|
+
pageGuid: this.pageGuid
|
|
82
|
+
},
|
|
83
|
+
framework: 'playwright'
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
log.debug(`Response ${JSON.stringify(response)}`);
|
|
87
|
+
const responseValue = JSON.parse(response.value);
|
|
88
|
+
if (!responseValue.success) {
|
|
89
|
+
throw new Error('Failed to get screenshots from Automate.' + ' Check dashboard for error.');
|
|
90
|
+
}
|
|
91
|
+
const tiles = [];
|
|
92
|
+
log.debug('Capturing tiles');
|
|
93
|
+
const tileResponse = JSON.parse(responseValue.result);
|
|
94
|
+
log.debug(`Tiles captured successfully ${JSON.stringify(tileResponse)}`);
|
|
95
|
+
for (let tileData of tileResponse.tiles) {
|
|
96
|
+
tiles.push(new Tile({
|
|
97
|
+
statusBarHeight: tileData.status_bar || 0,
|
|
98
|
+
navBarHeight: tileData.nav_bar || 0,
|
|
99
|
+
headerHeight: tileData.header_height || 0,
|
|
100
|
+
footerHeight: tileData.footer_height || 0,
|
|
101
|
+
fullscreen,
|
|
102
|
+
sha: tileData.sha.split('-')[0] // drop build id
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const tagData = {
|
|
107
|
+
width: tileResponse.comparison_tag_data.width,
|
|
108
|
+
height: tileResponse.comparison_tag_data.height,
|
|
109
|
+
resolution: tileResponse.comparison_tag_data.resolution
|
|
110
|
+
};
|
|
111
|
+
const ignoreRegionsData = tileResponse.ignore_regions_data || [];
|
|
112
|
+
const considerRegionsData = tileResponse.consider_regions_data || [];
|
|
113
|
+
const metadata = {
|
|
114
|
+
screenshotType: screenshotType
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
tiles: tiles,
|
|
118
|
+
domInfoSha: tileResponse.dom_sha,
|
|
119
|
+
metadata: metadata,
|
|
120
|
+
tagData: tagData,
|
|
121
|
+
ignoreRegionsData: ignoreRegionsData,
|
|
122
|
+
considerRegionsData: considerRegionsData
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async getTag(tagData) {
|
|
126
|
+
if (!this.automateResults) throw new Error('Comparison tag details not available');
|
|
127
|
+
const mobileOS = ['ANDROID'];
|
|
128
|
+
const normalizeTags = new NormalizeData();
|
|
129
|
+
const automateCaps = this.automateResults.capabilities;
|
|
130
|
+
const osName = normalizeTags.osRollUp(automateCaps.os);
|
|
131
|
+
const device = mobileOS.includes(osName.toUpperCase());
|
|
132
|
+
tagData.device = device;
|
|
133
|
+
console.log(`Device: ${device}`);
|
|
134
|
+
return await super.getTag(tagData);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -3,7 +3,17 @@ import AutomateProvider from './automateProvider.js';
|
|
|
3
3
|
export default class ProviderResolver {
|
|
4
4
|
static resolve(sessionId, commandExecutorUrl, capabilities, sessionCapabilities, clientInfo, environmentInfo, options, buildInfo) {
|
|
5
5
|
// We can safely do [0] because GenericProvider is catch all
|
|
6
|
-
const Klass = [AutomateProvider, GenericProvider].filter(
|
|
7
|
-
|
|
6
|
+
const Klass = [AutomateProvider, GenericProvider].filter(provider => provider.supports(commandExecutorUrl))[0];
|
|
7
|
+
const args = {
|
|
8
|
+
sessionId,
|
|
9
|
+
commandExecutorUrl,
|
|
10
|
+
capabilities,
|
|
11
|
+
sessionCapabilities,
|
|
12
|
+
clientInfo,
|
|
13
|
+
environmentInfo,
|
|
14
|
+
options,
|
|
15
|
+
buildInfo
|
|
16
|
+
};
|
|
17
|
+
return new Klass(args);
|
|
8
18
|
}
|
|
9
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/webdriver-utils",
|
|
3
|
-
"version": "1.28.8-beta.
|
|
3
|
+
"version": "1.28.8-beta.3",
|
|
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.28.8-beta.
|
|
33
|
-
"@percy/sdk-utils": "1.28.8-beta.
|
|
32
|
+
"@percy/config": "1.28.8-beta.3",
|
|
33
|
+
"@percy/sdk-utils": "1.28.8-beta.3"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "60fcc0cc0e65b1dd9f81286a3a3cd7383f1f6220"
|
|
36
36
|
}
|