@percy/webdriver-utils 1.27.5-beta.0 → 1.27.5-beta.1

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 CHANGED
@@ -71,4 +71,29 @@ export default class Driver {
71
71
  const response = JSON.parse((await request(baseUrl, options)).body);
72
72
  return response.value;
73
73
  }
74
+ async findElementBoundingBox(using, value) {
75
+ if (using === 'xpath') {
76
+ return await this.findElementXpath(value);
77
+ } else if (using === 'css selector') {
78
+ return await this.findElementSelector(value);
79
+ }
80
+ }
81
+ async findElementXpath(xpath) {
82
+ xpath = xpath.replace(/'/g, '"');
83
+ const command = {
84
+ script: `return document.evaluate('${xpath}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect();`,
85
+ args: []
86
+ };
87
+ const response = await this.executeScript(command);
88
+ return response.value;
89
+ }
90
+ async findElementSelector(selector) {
91
+ selector = selector.replace('\\', '\\\\');
92
+ const command = {
93
+ script: `return document.querySelector('${selector}').getBoundingClientRect();`,
94
+ args: []
95
+ };
96
+ const response = await this.executeScript(command);
97
+ return response.value;
98
+ }
74
99
  }
@@ -21,6 +21,14 @@ export default class GenericProvider {
21
21
  this.debugUrl = null;
22
22
  this.header = 0;
23
23
  this.footer = 0;
24
+ this.statusBarHeight = 0;
25
+ this.pageXShiftFactor = 0;
26
+ this.pageYShiftFactor = 0;
27
+ this.currentTag = null;
28
+ this.removeElementShiftFactor = 50000;
29
+ this.initialScrollFactor = {
30
+ value: [0, 0]
31
+ };
24
32
  }
25
33
  addDefaultOptions() {
26
34
  this.options.freezeAnimation = this.options.freezeAnimatedImage || this.options.freezeAnimation || false;
@@ -45,6 +53,22 @@ export default class GenericProvider {
45
53
  if (i) this.environmentInfo.add(i);
46
54
  }
47
55
  }
56
+ async getInitialPosition() {
57
+ if (this.currentTag.osName === 'iOS') {
58
+ this.initialScrollFactor = await this.driver.executeScript({
59
+ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];',
60
+ args: []
61
+ });
62
+ }
63
+ }
64
+ async scrollToInitialPosition(x, y) {
65
+ if (this.currentTag.osName === 'iOS') {
66
+ await this.driver.executeScript({
67
+ script: `window.scrollTo(${x}, ${y})`,
68
+ args: []
69
+ });
70
+ }
71
+ }
48
72
  async screenshot(name, {
49
73
  ignoreRegionXpaths = [],
50
74
  ignoreRegionSelectors = [],
@@ -63,6 +87,8 @@ export default class GenericProvider {
63
87
  log.debug(`[${name}] : Tag ${JSON.stringify(tag)}`);
64
88
  const tiles = await this.getTiles(this.header, this.footer, fullscreen);
65
89
  log.debug(`[${name}] : Tiles ${JSON.stringify(tiles)}`);
90
+ this.currentTag = tag;
91
+ this.statusBarHeight = tiles.tiles[0].statusBarHeight;
66
92
  const ignoreRegions = await this.findRegions(ignoreRegionXpaths, ignoreRegionSelectors, ignoreRegionElements, customIgnoreRegions);
67
93
  const considerRegions = await this.findRegions(considerRegionXpaths, considerRegionSelectors, considerRegionElements, customConsiderRegions);
68
94
  await this.setDebugUrl();
@@ -144,29 +170,62 @@ export default class GenericProvider {
144
170
  async setDebugUrl() {
145
171
  this.debugUrl = 'https://localhost/v1';
146
172
  }
173
+ async doTransformations() {
174
+ const hideScrollbarStyle = `
175
+ /* Hide scrollbar for Chrome, Safari and Opera */
176
+ ::-webkit-scrollbar {
177
+ display: none !important;
178
+ }
179
+
180
+ /* Hide scrollbar for IE, Edge and Firefox */
181
+ body, html {
182
+ -ms-overflow-style: none !important; /* IE and Edge */
183
+ scrollbar-width: none !important; /* Firefox */
184
+ }`.replace(/\n/g, '');
185
+ const jsScript = `
186
+ const e = document.createElement('style');
187
+ e.setAttribute('class', 'poa-injected');
188
+ e.innerHTML = '${hideScrollbarStyle}'
189
+ document.head.appendChild(e);`;
190
+ await this.driver.executeScript({
191
+ script: jsScript,
192
+ args: []
193
+ });
194
+ }
195
+ async undoTransformations(data) {
196
+ const jsScript = `
197
+ const n = document.querySelectorAll('${data}');
198
+ n.forEach((e) => {e.remove()});`;
199
+ await this.driver.executeScript({
200
+ script: jsScript,
201
+ args: []
202
+ });
203
+ }
147
204
  async findRegions(xpaths, selectors, elements, customLocations) {
148
- const xpathRegions = await this.getSeleniumRegionsBy('xpath', xpaths);
149
- const selectorRegions = await this.getSeleniumRegionsBy('css selector', selectors);
150
- const elementRegions = await this.getSeleniumRegionsByElement(elements);
151
- const customRegions = await this.getSeleniumRegionsByLocation(customLocations);
152
- return [...xpathRegions, ...selectorRegions, ...elementRegions, ...customRegions];
205
+ let isRegionPassed = [xpaths, selectors, elements, customLocations].some(regions => regions.length > 0);
206
+ if (isRegionPassed) {
207
+ await this.doTransformations();
208
+ const xpathRegions = await this.getSeleniumRegionsBy('xpath', xpaths);
209
+ const selectorRegions = await this.getSeleniumRegionsBy('css selector', selectors);
210
+ const elementRegions = await this.getSeleniumRegionsByElement(elements);
211
+ const customRegions = await this.getSeleniumRegionsByLocation(customLocations);
212
+ await this.undoTransformations('.poa-injected');
213
+ return [...xpathRegions, ...selectorRegions, ...elementRegions, ...customRegions];
214
+ } else {
215
+ return [];
216
+ }
153
217
  }
154
- async getRegionObject(selector, elementId) {
155
- const scaleFactor = parseInt(await this.metaData.devicePixelRatio());
156
- const rect = await this.driver.rect(elementId);
157
- const location = {
158
- x: rect.x,
159
- y: rect.y
160
- };
161
- const size = {
162
- height: rect.height,
163
- width: rect.width
164
- };
218
+ async getRegionObjectFromBoundingBox(selector, element) {
219
+ const scaleFactor = await this.metaData.devicePixelRatio();
220
+ let headerAdjustment = 0;
221
+ if (this.currentTag.osName === 'iOS') {
222
+ headerAdjustment = this.statusBarHeight;
223
+ }
165
224
  const coOrdinates = {
166
- top: Math.floor(location.y * scaleFactor),
167
- bottom: Math.ceil((location.y + size.height) * scaleFactor),
168
- left: Math.floor(location.x * scaleFactor),
169
- right: Math.ceil((location.x + size.width) * scaleFactor)
225
+ top: Math.floor(element.y * scaleFactor) + Math.floor(headerAdjustment),
226
+ bottom: Math.ceil((element.y + element.height) * scaleFactor) + Math.ceil(headerAdjustment),
227
+ left: Math.floor(element.x * scaleFactor),
228
+ right: Math.ceil((element.x + element.width) * scaleFactor)
170
229
  };
171
230
  const jsonObject = {
172
231
  selector,
@@ -178,9 +237,9 @@ export default class GenericProvider {
178
237
  const regionsArray = [];
179
238
  for (const idx in elements) {
180
239
  try {
181
- const element = await this.driver.findElement(findBy, elements[idx]);
240
+ const boundingBoxRegion = await this.driver.findElementBoundingBox(findBy, elements[idx]);
182
241
  const selector = `${findBy}: ${elements[idx]}`;
183
- const region = await this.getRegionObject(selector, element[Object.keys(element)[0]]);
242
+ const region = await this.getRegionObjectFromBoundingBox(selector, boundingBoxRegion);
184
243
  regionsArray.push(region);
185
244
  } catch (e) {
186
245
  log.warn(`Selenium Element with ${findBy}: ${elements[idx]} not found. Ignoring this ${findBy}.`);
@@ -189,8 +248,57 @@ export default class GenericProvider {
189
248
  }
190
249
  return regionsArray;
191
250
  }
251
+ async updatePageShiftFactor(location, scaleFactor) {
252
+ const scrollFactors = await this.driver.executeScript({
253
+ script: 'return [parseInt(window.scrollX), parseInt(window.scrollY)];',
254
+ args: []
255
+ });
256
+ if (this.currentTag.osName === 'iOS' || this.currentTag.osName === 'OS X' && parseInt(this.currentTag.browserVersion) > 13 && this.currentTag.browserName.toLowerCase() === 'safari') {
257
+ this.pageYShiftFactor = this.statusBarHeight;
258
+ } else {
259
+ this.pageYShiftFactor = this.statusBarHeight - scrollFactors.value[1] * scaleFactor;
260
+ }
261
+ this.pageXShiftFactor = this.currentTag.osName === 'iOS' ? 0 : -(scrollFactors.value[0] * scaleFactor);
262
+ if (this.currentTag.osName === 'iOS') {
263
+ if (scrollFactors.value[0] !== this.initialScrollFactor.value[0] || scrollFactors.value[1] !== this.initialScrollFactor.value[1]) {
264
+ this.pageXShiftFactor = -1 * this.removeElementShiftFactor;
265
+ this.pageYShiftFactor = -1 * this.removeElementShiftFactor;
266
+ } else if (location.y === 0) {
267
+ this.pageYShiftFactor += -(scrollFactors.value[1] * scaleFactor);
268
+ }
269
+ }
270
+ }
271
+ async getRegionObject(selector, elementId) {
272
+ const scaleFactor = await this.metaData.devicePixelRatio();
273
+ const rect = await this.driver.rect(elementId);
274
+ const location = {
275
+ x: rect.x,
276
+ y: rect.y
277
+ };
278
+ const size = {
279
+ height: rect.height,
280
+ width: rect.width
281
+ };
282
+ // Update pageShiftFactor Element is not visible in viewport
283
+ // In case of iOS if the element is not visible in viewport it gives 0 for x-y coordinate.
284
+ // In case of iOS if the element is partially visible it gives negative x-y coordinate.
285
+ // Subtracting ScrollY/ScrollX ensures if the element is visible in viewport or not.
286
+ await this.updatePageShiftFactor(location, scaleFactor);
287
+ const coOrdinates = {
288
+ top: Math.floor(location.y * scaleFactor) + Math.floor(this.pageYShiftFactor),
289
+ bottom: Math.ceil((location.y + size.height) * scaleFactor) + Math.ceil(this.pageYShiftFactor),
290
+ left: Math.floor(location.x * scaleFactor) + Math.floor(this.pageXShiftFactor),
291
+ right: Math.ceil((location.x + size.width) * scaleFactor) + Math.ceil(this.pageXShiftFactor)
292
+ };
293
+ const jsonObject = {
294
+ selector,
295
+ coOrdinates
296
+ };
297
+ return jsonObject;
298
+ }
192
299
  async getSeleniumRegionsByElement(elements) {
193
300
  const regionsArray = [];
301
+ await this.getInitialPosition();
194
302
  for (let index = 0; index < elements.length; index++) {
195
303
  try {
196
304
  const selector = `element: ${index}`;
@@ -201,6 +309,7 @@ export default class GenericProvider {
201
309
  log.debug(e.toString());
202
310
  }
203
311
  }
312
+ await this.scrollToInitialPosition(this.initialScrollFactor.value[0], this.initialScrollFactor.value[1]);
204
313
  return regionsArray;
205
314
  }
206
315
  async getSeleniumRegionsByLocation(customLocations) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/webdriver-utils",
3
- "version": "1.27.5-beta.0",
3
+ "version": "1.27.5-beta.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "publishConfig": {
11
11
  "access": "public",
12
- "tag": "latest"
12
+ "tag": "beta"
13
13
  },
14
14
  "engines": {
15
15
  "node": ">=14"
@@ -29,8 +29,8 @@
29
29
  "test:coverage": "yarn test --coverage"
30
30
  },
31
31
  "dependencies": {
32
- "@percy/config": "1.27.5-beta.0",
33
- "@percy/sdk-utils": "1.27.5-beta.0"
32
+ "@percy/config": "1.27.5-beta.1",
33
+ "@percy/sdk-utils": "1.27.5-beta.1"
34
34
  },
35
- "gitHead": "5fed59aa21112e7854a414306504ce7243df7365"
35
+ "gitHead": "f5924b050d0c04f8ac63d610af291b32f51565f8"
36
36
  }