@percy/webdriver-utils 1.28.8-beta.0 → 1.28.8-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/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 automateScreenshot({
5
+ static async captureScreenshot({
5
6
  sessionId,
6
7
  commandExecutorUrl,
7
8
  capabilities,
8
- sessionCapabilites,
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:automateScreenshot');
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
- const automate = ProviderResolver.resolve(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo);
20
- log.debug(`[${snapshotName}] : Resolved provider ...`);
21
- await automate.createDriver();
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 automate.screenshot(snapshotName, options);
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$options2;
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$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.fullPage ? 'fullpage' : 'singlepage';
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 _automateCaps$os_vers, _ref;
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 = (_ref = this.metaData.orientation() || automateCaps.deviceOrientation) === null || _ref === void 0 ? void 0 : _ref.toLowerCase();
186
- return {
187
- name: deviceName,
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
- browserName,
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 MetaDataResolver from '../metadata/metaDataResolver.js';
2
+ import TimeIt from '../util/timing.js';
3
3
  import Tile from '../util/tile.js';
4
- import Driver from '../driver.js';
4
+ import Driver from '../../src/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
- clientInfo = new Set();
8
- environmentInfo = new Set();
9
- options = {};
10
- constructor(sessionId, commandExecutorUrl, capabilities, sessionCapabilites, clientInfo, environmentInfo, options, buildInfo) {
11
- this.sessionId = sessionId;
12
- this.commandExecutorUrl = commandExecutorUrl;
13
- this.capabilities = capabilities;
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 = await MetaDataResolver.resolve(this.driver, caps, this.capabilities);
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.clientInfo.add(i);
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.environmentInfo.add(i);
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: [...this.environmentInfo].join('; '),
114
- clientInfo: [...this.clientInfo].join(' '),
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$options;
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$options = this.options) !== null && _this$options !== void 0 && _this$options.fullPage || this.isIOS()) {
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$options2;
280
+ var _this$options3;
229
281
  const scaleFactor = await this.metaData.devicePixelRatio();
230
282
  let scrollX = 0,
231
283
  scrollY = 0;
232
- if ((_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.fullPage) {
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$options3;
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$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.fullPage)) {
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$options4;
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$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.fullPage) {
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(x => x.supports(commandExecutorUrl))[0];
7
- return new Klass(sessionId, commandExecutorUrl, capabilities, sessionCapabilities, clientInfo, environmentInfo, options, buildInfo);
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.0",
3
+ "version": "1.28.8-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.28.8-beta.0",
33
- "@percy/sdk-utils": "1.28.8-beta.0"
32
+ "@percy/config": "1.28.8-beta.2",
33
+ "@percy/sdk-utils": "1.28.8-beta.2"
34
34
  },
35
- "gitHead": "866f110837fabc0607d9b817b2e4373bc91bc474"
35
+ "gitHead": "6c954bcd0fa6a825ad636167f0a07251f124b710"
36
36
  }