How to use this.getSafariDeviceSize method in Appium Xcuitest Driver

Best JavaScript code snippet using appium-xcuitest-driver

Run Appium Xcuitest Driver automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Sign up Free
_

web.js

Source: web.js Github

copy
1import { iosCommands } from 'appium-ios-driver';
2import { retryInterval, waitForCondition } from 'asyncbox';
3import { util, timing } from 'appium-support';
4import log from '../logger';
5import _ from 'lodash';
6import B from 'bluebird';
7
8const IPHONE_TOP_BAR_HEIGHT = 71;
9const IPHONE_SCROLLED_TOP_BAR_HEIGHT = 41;
10const IPHONE_X_SCROLLED_OFFSET = 55;
11const IPHONE_X_NOTCH_OFFSET_IOS = 24;
12const IPHONE_X_NOTCH_OFFSET_IOS_13 = 20;
13
14const IPHONE_LANDSCAPE_TOP_BAR_HEIGHT = 51;
15const IPHONE_BOTTOM_BAR_OFFSET = 49;
16const TAB_BAR_OFFSET = 33;
17const IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET = 84;
18const IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET = 95;
19
20const NOTCHED_DEVICE_SIZES = [
21  {w: 1125, h: 2436}, // 11 Pro, X, Xs
22  {w: 828, h: 1792}, // 11, Xr
23  {w: 1242, h: 2688}, // 11 Pro Max, Xs Max
24];
25
26const ATOM_WAIT_TIMEOUT = 2 * 60000;
27const ATOM_WAIT_ALERT_WAIT = 400;
28
29let extensions = {};
30
31Object.assign(extensions, iosCommands.web);
32
33
34
35extensions.getSafariIsIphone = _.memoize(async function getSafariIsIphone () {
36  try {
37    const userAgent = await this.execute('return navigator.userAgent');
38    return userAgent.toLowerCase().includes('iphone');
39  } catch (err) {
40    log.warn(`Unable to find device type from useragent. Assuming iPhone`);
41    log.debug(`Error: ${err.message}`);
42  }
43  return true;
44});
45
46extensions.getSafariDeviceSize = _.memoize(async function getSafariDeviceSize () {
47  const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
48  const {width, height} = await this.execute(script);
49  const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
50  return {
51    width: normWidth,
52    height: normHeight,
53  };
54});
55
56extensions.getSafariIsNotched = _.memoize(async function getSafariIsNotched () {
57  try {
58    const {width, height} = await this.getSafariDeviceSize();
59    for (const device of NOTCHED_DEVICE_SIZES) {
60      if (device.w === width && device.h === height) {
61        return true;
62      }
63    }
64  } catch (err) {
65    log.warn(`Unable to find device type from dimensions. Assuming the device is not notched`);
66    log.debug(`Error: ${err.message}`);
67  }
68  return false;
69});
70
71extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWebCoordsOffset (wvPos, realDims) {
72  let topOffset = 0;
73  let bottomOffset = 0;
74
75  const isIphone = await this.getSafariIsIphone();
76  const isNotched = isIphone && await this.getSafariIsNotched();
77
78  const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';
79
80  const notchOffset = isNotched
81    ? util.compareVersions(this.opts.platformVersion, '=', '13.0')
82      ? IPHONE_X_NOTCH_OFFSET_IOS_13
83      : IPHONE_X_NOTCH_OFFSET_IOS
84    : 0;
85
86  const isScrolled = await this.execute('return document.documentElement.scrollTop > 0');
87  if (isScrolled) {
88    topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;
89
90    if (isNotched) {
91      topOffset -= IPHONE_X_SCROLLED_OFFSET;
92    }
93
94    // If the iPhone is landscape then there is no top bar
95    if (orientation === 'LANDSCAPE' && isIphone) {
96      topOffset = 0;
97    }
98  } else {
99    topOffset = IPHONE_TOP_BAR_HEIGHT + notchOffset;
100
101    if (isIphone) {
102      if (orientation === 'PORTRAIT') {
103        // The bottom bar is only visible when portrait
104        bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
105      } else {
106        topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
107      }
108    }
109
110    if (orientation === 'LANDSCAPE' || !isIphone) {
111      // Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
112      const tabs = await this.findNativeElementOrElements('-ios predicate string', `name LIKE '*, Tab' AND visible = 1`, true);
113      if (tabs.length > 0) {
114        topOffset += TAB_BAR_OFFSET;
115      }
116    }
117  }
118
119  topOffset += await this.getExtraNativeWebTapOffset();
120
121  wvPos.y += topOffset;
122  realDims.h -= (topOffset + bottomOffset);
123};
124
125extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffset () {
126  let offset = 0;
127
128  // try to see if there is an Smart App Banner
129  const banners = await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true);
130  if (banners.length > 0) {
131    offset += await this.getSafariIsIphone() ?
132      IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET :
133      IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
134  }
135
136  log.debug(`Additional native web tap offset computed: ${offset}`);
137  return offset;
138};
139
140async function tapWebElementNatively (driver, atomsElement) {
141  // try to get the text of the element, which will be accessible in the
142  // native context
143  try {
144    let text = await driver.executeAtom('get_text', [atomsElement]);
145    if (!text) {
146      text = await driver.executeAtom('get_attribute_value', [atomsElement, 'value']);
147    }
148
149    if (text) {
150      const els = await driver.findNativeElementOrElements('accessibility id', text, true);
151      if (els.length === 1 || els.length === 2) {
152        const el = els[0];
153        // use tap because on iOS 11.2 and below `nativeClick` crashes WDA
154        const rect = await driver.proxyCommand(`/element/${util.unwrapElement(el)}/rect`, 'GET');
155        if (els.length === 2) {
156          const el2 = els[1];
157          const rect2 = await driver.proxyCommand(`/element/${util.unwrapElement(el2)}/rect`, 'GET');
158
159          if ((rect.x !== rect2.x || rect.y !== rect2.y) ||
160          (rect.width !== rect2.width || rect.height !== rect2.height)) {
161            // These 2 native elements are not referring to the same web element
162            return false;
163          }
164        }
165        const coords = {
166          x: Math.round(rect.x + rect.width / 2),
167          y: Math.round(rect.y + rect.height / 2),
168        };
169        await driver.clickCoords(coords);
170        return true;
171      }
172    }
173  } catch (err) {
174    // any failure should fall through and trigger the more elaborate
175    // method of clicking
176    log.warn(`Error attempting to click: ${err.message}`);
177  }
178  return false;
179}
180
181extensions.nativeWebTap = async function nativeWebTap (el) {
182  const atomsElement = this.useAtomsElement(el);
183
184  // if strict native tap, do not try to do it with WDA directly
185  if (!(await this.settings.getSettings()).nativeWebTapStrict && await tapWebElementNatively(this, atomsElement)) {
186    return;
187  }
188  log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
189
190  // `get_top_left_coordinates` returns the wrong value sometimes,
191  // unless we pre-call both of these functions before the actual calls
192  await this.executeAtom('get_size', [atomsElement]);
193  await this.executeAtom('get_top_left_coordinates', [atomsElement]);
194
195  const {width, height} = await this.executeAtom('get_size', [atomsElement]);
196  let {x, y} = await this.executeAtom('get_top_left_coordinates', [atomsElement]);
197  x += width / 2;
198  y += height / 2;
199
200  this.curWebCoords = {x, y};
201  await this.clickWebCoords();
202};
203
204extensions.clickCoords = async function clickCoords (coords) {
205  await this.performTouch([
206    {
207      action: 'tap',
208      options: coords,
209    },
210  ]);
211};
212
213extensions.translateWebCoords = async function translateWebCoords (coords) {
214  log.debug(`Translating coordinates (${JSON.stringify(coords)}) to web coordinates`);
215
216  // absolutize web coords
217  let webview = await retryInterval(5, 100, async () => {
218    const webviews = await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', true);
219    if (webviews.length === 0) {
220      throw new Error(`No webviews found. Unable to translate web coordinates for native web tap`);
221    }
222    return webviews[0];
223  });
224  webview = util.unwrapElement(webview);
225
226  const rect = await this.proxyCommand(`/element/${webview}/rect`, 'GET');
227  const wvPos = {x: rect.x, y: rect.y};
228  const realDims = {w: rect.width, h: rect.height};
229
230  const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
231  const wvDims = await this.remote.execute(cmd);
232
233  await this.getExtraTranslateWebCoordsOffset(wvPos, realDims);
234
235  if (wvDims && realDims && wvPos) {
236    let xRatio = realDims.w / wvDims.w;
237    let yRatio = realDims.h / wvDims.h;
238    let newCoords = {
239      x: wvPos.x + Math.round(xRatio * coords.x),
240      y: wvPos.y + Math.round(yRatio * coords.y),
241    };
242
243    // additional logging for coordinates, since it is sometimes broken
244    //   see https://github.com/appium/appium/issues/9159
245    log.debug(`Converted coordinates: ${JSON.stringify(newCoords)}`);
246    log.debug(`    rect: ${JSON.stringify(rect)}`);
247    log.debug(`    wvPos: ${JSON.stringify(wvPos)}`);
248    log.debug(`    realDims: ${JSON.stringify(realDims)}`);
249    log.debug(`    wvDims: ${JSON.stringify(wvDims)}`);
250    log.debug(`    xRatio: ${JSON.stringify(xRatio)}`);
251    log.debug(`    yRatio: ${JSON.stringify(yRatio)}`);
252
253    log.debug(`Converted web coords ${JSON.stringify(coords)} ` +
254              `into real coords ${JSON.stringify(newCoords)}`);
255    return newCoords;
256  }
257};
258
259extensions.checkForAlert = async function checkForAlert () {
260  return _.isString(await this.getAlertText());
261};
262
263extensions.waitForAtom = async function waitForAtom (promise) {
264  const timer = new timing.Timer().start();
265
266  // need to check for alert while the atom is being executed.
267  // so notify ourselves when it happens
268  let done = false;
269  let error = null;
270  promise = B.resolve(promise) // eslint-disable-line promise/catch-or-return
271    .timeout(ATOM_WAIT_TIMEOUT)
272    .catch(function (err) { // eslint-disable-line promise/prefer-await-to-callbacks
273      log.debug(`Error received while executing atom: ${err.message}`);
274      if (err instanceof B.TimeoutError) {
275        err = new Error(`Did not get any response for atom execution after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
276      }
277      // save and check later, or an Unhandled rejection will be reported
278      error = err;
279    })
280    .finally(function () {
281      done = true;
282    });
283
284  // try ten times to check alert
285  for (let i = 0; i < 10; i++) {
286    // pause, or the atom promise is resolved
287    try {
288      await waitForCondition(() => done, {
289        waitMs: ATOM_WAIT_ALERT_WAIT,
290        intervalMs: 0, // just for the pause in execution
291      });
292      // `done` became true, so atom promise is resolved
293      break;
294    } catch (ign) {
295      // `done` never became true, so move on to trying to find an alert
296    }
297
298    // check if there is an alert, or the atom promise is resolved
299    try {
300      const res = await B.any([this.checkForAlert(), promise]);
301      if (error) {
302        throw error;
303      }
304      return this.parseExecuteResponse(res);
305    } catch (err) {
306      // no alert found, so pass through
307      log.debug(`No alert found: ${err.message}`);
308    }
309  }
310
311  // at this point, all that can be done is wait for the atom promise to be
312  // resolved
313  const res = await promise;
314  if (error) {
315    throw error;
316  }
317  return this.parseExecuteResponse(res);
318};
319
320extensions.mobileWebNav = async function mobileWebNav (navType) {
321  this.remote.allowNavigationWithoutReload = true;
322  try {
323    await this.executeAtom('execute_script', [`history.${navType}();`, null]);
324  } finally {
325    this.remote.allowNavigationWithoutReload = false;
326  }
327};
328
329
330export default extensions;
331
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Appium Xcuitest Driver on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)