Skip to content

Commit

Permalink
Only native tap if there is one matching element (#947)
Browse files Browse the repository at this point in the history
* [RD-34730] Only native tap if there is one element matching the accessibility id.

* [RD-34750] Refactored web coords offset calculations
* Replaced Tab bar and URL bar driver calls with constants.
* Cached isIphone checks.
* Reduced number of constants.

* [RD-34750] Added condition to allow anchor links to be tapped natively.

* [RD-34750] Changed command to get webview dimensions
* The old method would return the full height off the page including overflow, which would break the ratio calculation.
  • Loading branch information
erustusagutu authored and imurchie committed May 13, 2019
1 parent d2f4756 commit c603443
Showing 1 changed file with 68 additions and 84 deletions.
152 changes: 68 additions & 84 deletions lib/commands/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ import log from '../logger';
import _ from 'lodash';
import B from 'bluebird';


const IPHONE_EXTRA_WEB_COORD_SCROLL_OFFSET = -15;
const IPHONE_EXTRA_WEB_COORD_NON_SCROLL_OFFSET = 10;
const IPHONE_WEB_COORD_OFFSET = -10;
const IPHONE_TOP_BAR_HEIGHT = 71;
const IPHONE_SCROLLED_TOP_BAR_HEIGHT = 41;
const IPHONE_X_NOTCH_OFFSET = 24;
const IPHONE_LANDSCAPE_TOP_BAR_HEIGHT = 51;
const IPHONE_BOTTOM_BAR_OFFSET = 49;
const TAB_BAR_OFFSET = 33;
const IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET = 84;
const IPHONE_X_EXTRA_WEB_COORD_SCROLL_OFFSET = -90;
const IPHONE_X_EXTRA_WEB_COORD_NON_SCROLL_OFFSET = -10;
const IPHONE_X_WEB_COORD_OFFSET = 40;
const IPAD_EXTRA_WEB_COORD_SCROLL_OFFSET = -10;
const IPAD_EXTRA_WEB_COORD_NON_SCROLL_OFFSET = 0;
const IPAD_WEB_COORD_OFFSET = 10;
const IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET = 95;

const IPHONE_X_WIDTH = 375;
Expand All @@ -29,7 +25,7 @@ Object.assign(extensions, iosCommands.web);



extensions.getSafariIsIphone = async function getSafariIsIphone () {
extensions.getSafariIsIphone = _.memoize(async function getSafariIsIphone () {
try {
const userAgent = await this.execute('return navigator.userAgent');
return userAgent.toLowerCase().includes('iphone');
Expand All @@ -38,82 +34,78 @@ extensions.getSafariIsIphone = async function getSafariIsIphone () {
log.debug(`Error: ${err.message}`);
}
return true;
};
});

extensions.getSafariIsIphoneX = async function getSafariIsIphone () {
extensions.getSafariIsIphoneX = _.memoize(async function getSafariIsIphone () {
try {
const script = 'return {height: window.screen.availHeight, width: window.screen.availWidth};';
const {height, width} = await this.execute(script);
// check for the correct height and width
return (height === IPHONE_X_HEIGHT && width === IPHONE_X_WIDTH) ||
(height === IPHONE_X_WIDTH && width === IPHONE_X_HEIGHT);
} catch (err) {
log.warn(`Unable to find device type from useragent. Assuming not iPhone X`);
log.warn(`Unable to find device type from dimensions. Assuming not iPhone X`);
log.debug(`Error: ${err.message}`);
}
return false;
};

const getElementHeightMemoized = _.memoize(async function getElementHeightMemoized (key, driver, el) {
el = util.unwrapElement(el);
return (await driver.getNativeRect(el)).height;
});

extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWebCoordsOffset (coords, webviewRect) {
let offset = 0;
extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWebCoordsOffset (wvPos, realDims) {
let topOffset = 0;
let bottomOffset = 0;

// keep track of implicit wait, and set locally to 0
const implicitWaitMs = this.implicitWaitMs;

const isIphone = await this.getSafariIsIphone();
const isIphoneX = isIphone && await this.getSafariIsIphoneX();

const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';

try {
this.setImplicitWait(0);

// check if the full url bar is up
await this.findNativeElementOrElements('accessibility id', 'ReloadButton', false);

// reload button found, which means scrolling has not happened
if (isIphoneX) {
offset += IPHONE_X_EXTRA_WEB_COORD_NON_SCROLL_OFFSET;
} else if (isIphone) {
offset += IPHONE_EXTRA_WEB_COORD_NON_SCROLL_OFFSET;
} else {
offset += IPAD_EXTRA_WEB_COORD_NON_SCROLL_OFFSET;
topOffset = IPHONE_TOP_BAR_HEIGHT + (isIphoneX ? IPHONE_X_NOTCH_OFFSET : 0);
if (isIphone) {
if (orientation === 'PORTRAIT') {
// The bottom bar is only visible when portrait
bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
} else {
topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
}
}
if (orientation === 'LANDSCAPE' || !isIphone) {
// Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
try {
await this.findNativeElementOrElements('-ios predicate string', `name LIKE '*, Tab' AND visible = 1`, false);
topOffset += TAB_BAR_OFFSET;
} catch (ign) {
// no element found, so no tabs and no need to deal with offset
}
}

} catch (err) {
// no reload button, which indicates scrolling has happened
// the URL bar may or may not be visible
try {
const el = await this.findNativeElementOrElements('accessibility id', 'URL', false);
offset -= await getElementHeightMemoized('URLBar', this, el);
} catch (ign) {
// no URL elements found, so continue
topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + (isIphoneX ? IPHONE_X_NOTCH_OFFSET : 0);

// If the iPhone is landscape then there is not top bar
if (orientation === 'LANDSCAPE' && isIphone) {
topOffset = 0;
}

} finally {
// return implicit wait to what it was
this.setImplicitWait(implicitWaitMs);
}

if (coords.y > webviewRect.height) {
// when scrolling has happened, there is a tick more offset needed
if (isIphoneX) {
offset += IPHONE_X_EXTRA_WEB_COORD_SCROLL_OFFSET;
} else if (isIphone) {
offset += IPHONE_EXTRA_WEB_COORD_SCROLL_OFFSET;
} else {
offset += IPAD_EXTRA_WEB_COORD_SCROLL_OFFSET;
}
}

// extra offset necessary
offset += isIphone ? IPHONE_WEB_COORD_OFFSET : IPAD_WEB_COORD_OFFSET;
topOffset += await this.getExtraNativeWebTapOffset();

offset += isIphoneX ? IPHONE_X_WEB_COORD_OFFSET : 0;

log.debug(`Extra translated web coordinates offset: ${offset}`);
return offset;
wvPos.y += topOffset;
realDims.h -= (topOffset + bottomOffset);
};

extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffset () {
Expand All @@ -124,15 +116,7 @@ extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffse
try {
this.setImplicitWait(0);

// first try to get tab offset
try {
const el = await this.findNativeElementOrElements('-ios predicate string', `name LIKE '*, Tab' AND visible = 1`, false);
offset += await getElementHeightMemoized('TabBar', this, el);
} catch (ign) {
// no element found, so no tabs and no need to deal with offset
}

// next try to see if there is an Smart App Banner
// try to see if there is an Smart App Banner
try {
await this.findNativeElementOrElements('accessibility id', 'Close app download offer', false);
offset += await this.getSafariIsIphone() ?
Expand Down Expand Up @@ -160,15 +144,28 @@ async function tapWebElementNatively (driver, atomsElement) {
}

if (text) {
const el = await driver.findNativeElementOrElements('accessibility id', text, false);
// use tap because on iOS 11.2 and below `nativeClick` crashes WDA
const rect = await driver.proxyCommand(`/element/${el.ELEMENT}/rect`, 'GET');
const coords = {
x: Math.round(rect.x + rect.width / 2),
y: Math.round(rect.y + rect.height / 2),
};
await driver.clickCoords(coords);
return true;
const els = await driver.findNativeElementOrElements('accessibility id', text, true);
if (els.length === 1 || els.length === 2) {
const el = els[0];
// use tap because on iOS 11.2 and below `nativeClick` crashes WDA
const rect = await driver.proxyCommand(`/element/${el.ELEMENT}/rect`, 'GET');
if (els.length === 2) {
const el2 = els[1];
const rect2 = await driver.proxyCommand(`/element/${el2.ELEMENT}/rect`, 'GET');

if ((rect.x !== rect2.x || rect.y !== rect2.y) ||
(rect.width !== rect2.width || rect.height !== rect2.height)) {
// These 2 native elements are not referring to the same web element
return false;
}
}
const coords = {
x: Math.round(rect.x + rect.width / 2),
y: Math.round(rect.y + rect.height / 2),
};
await driver.clickCoords(coords);
return true;
}
}
} catch (err) {
// any failure should fall through and trigger the more elaborate
Expand Down Expand Up @@ -229,29 +226,17 @@ extensions.translateWebCoords = async function translateWebCoords (coords) {
const wvPos = {x: rect.x, y: rect.y};
const realDims = {w: rect.width, h: rect.height};

const cmd = '(function () { return {w: document.documentElement.clientWidth, h: document.documentElement.clientHeight}; })()';
const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
const wvDims = await this.remote.execute(cmd);

// TODO: investigate where these come from. They appear to be constants in my tests
const urlBarHeight = 64;
wvPos.y += urlBarHeight;

const realDimensionHeight = 108;
realDims.h -= realDimensionHeight;

// add static offset for safari in landscape mode
let yOffset = this.opts.curOrientation === 'LANDSCAPE' ? this.landscapeWebCoordsOffset : 0;

// add extra offset for possible extra things in the top of the page
yOffset += await this.getExtraNativeWebTapOffset();
coords.y += await this.getExtraTranslateWebCoordsOffset(coords, rect);
await this.getExtraTranslateWebCoordsOffset(wvPos, realDims);

if (wvDims && realDims && wvPos) {
let xRatio = realDims.w / wvDims.w;
let yRatio = realDims.h / wvDims.h;
let newCoords = {
x: wvPos.x + Math.round(xRatio * coords.x),
y: wvPos.y + yOffset + Math.round(yRatio * coords.y),
y: wvPos.y + Math.round(yRatio * coords.y),
};

// additional logging for coordinates, since it is sometimes broken
Expand All @@ -263,7 +248,6 @@ extensions.translateWebCoords = async function translateWebCoords (coords) {
log.debug(` wvDims: ${JSON.stringify(wvDims)}`);
log.debug(` xRatio: ${JSON.stringify(xRatio)}`);
log.debug(` yRatio: ${JSON.stringify(yRatio)}`);
log.debug(` yOffset: ${JSON.stringify(yOffset)}`);

log.debug(`Converted web coords ${JSON.stringify(coords)} ` +
`into real coords ${JSON.stringify(newCoords)}`);
Expand Down

0 comments on commit c603443

Please sign in to comment.