Skip to content

Commit

Permalink
feat: add nativeWebTapStrict cap (#1186)
Browse files Browse the repository at this point in the history
* fix: make sure native web tap works with full conversion works for newer devices

* fix: better way of checking notch

* feat: add nativeWebTapStrict cap

* fix: work on failing test
  • Loading branch information
imurchie authored Mar 25, 2020
1 parent 99b8cda commit f2ef0a9
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 273 deletions.
2 changes: 1 addition & 1 deletion lib/commands/gesture.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ commands.click = async function click (el) {
return await this.nativeClick(el);
}
el = util.unwrapElement(el);
if ((await this.settings.getSettings()).nativeWebTap) {
if ((await this.settings.getSettings()).nativeWebTap || (await this.settings.getSettings()).nativeWebTapStrict) {
// atoms-based clicks don't always work in safari 7
log.debug('Using native web tap');
await this.nativeWebTap(el);
Expand Down
129 changes: 60 additions & 69 deletions lib/commands/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import B from 'bluebird';

const IPHONE_TOP_BAR_HEIGHT = 71;
const IPHONE_SCROLLED_TOP_BAR_HEIGHT = 41;
const IPHONE_X_SCROLLED_OFFSET = 55;
const IPHONE_X_NOTCH_OFFSET_IOS = 24;
const IPHONE_X_NOTCH_OFFSET_IOS_13 = 20;

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 IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET = 95;

const IPHONE_X_WIDTH = 375;
const IPHONE_X_HEIGHT = 812;
const IPHONE_XR_WIDTH = 414;
const IPHONE_XR_HEIGHT = 896;
const NOTCHED_DEVICE_SIZES = [
{w: 1125, h: 2436}, // 11 Pro, X, Xs
{w: 828, h: 1792}, // 11, Xr
{w: 1242, h: 2688}, // 11 Pro Max, Xs Max
];

const ATOM_WAIT_TIMEOUT = 5 * 60000;
const ATOM_WAIT_INITIAL_TIMEOUT = 1000;
Expand All @@ -41,17 +44,26 @@ extensions.getSafariIsIphone = _.memoize(async function getSafariIsIphone () {
return true;
});

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
const [portraitHeight, portraitWidth] = height > width ? [height, width] : [width, height];
return (portraitHeight === IPHONE_X_HEIGHT && portraitWidth === IPHONE_X_WIDTH) ||
(portraitHeight === IPHONE_XR_HEIGHT && portraitWidth === IPHONE_XR_WIDTH);
extensions.getSafariDeviceSize = _.memoize(async function getSafariDeviceSize () {
const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
const {width, height} = await this.execute(script);
const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
return {
width: normWidth,
height: normHeight,
};
});

extensions.getSafariIsNotched = _.memoize(async function getSafariIsNotched () {
try {
const {width, height} = await this.getSafariDeviceSize();
for (const device of NOTCHED_DEVICE_SIZES) {
if (device.w === width && device.h === height) {
return true;
}
}
} catch (err) {
log.warn(`Unable to find device type from dimensions. Assuming not iPhone X`);
log.warn(`Unable to find device type from dimensions. Assuming the device is not notched`);
log.debug(`Error: ${err.message}`);
}
return false;
Expand All @@ -61,28 +73,32 @@ extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWe
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 isNotched = isIphone && await this.getSafariIsNotched();

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

const notchOffset = isIphoneX
const notchOffset = isNotched
? util.compareVersions(this.opts.platformVersion, '=', '13.0')
? IPHONE_X_NOTCH_OFFSET_IOS_13
: IPHONE_X_NOTCH_OFFSET_IOS
: 0;

try {
this.setImplicitWait(0);
const isScrolled = await this.execute('return document.documentElement.scrollTop > 0');
if (isScrolled) {
topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;

// check if the full url bar is up
await this.findNativeElementOrElements('accessibility id', 'ReloadButton', false);
if (isNotched) {
topOffset -= IPHONE_X_SCROLLED_OFFSET;
}

// reload button found, which means scrolling has not happened
// If the iPhone is landscape then there is no top bar
if (orientation === 'LANDSCAPE' && isIphone) {
topOffset = 0;
}
} else {
topOffset = IPHONE_TOP_BAR_HEIGHT + notchOffset;

if (isIphone) {
if (orientation === 'PORTRAIT') {
// The bottom bar is only visible when portrait
Expand All @@ -91,28 +107,14 @@ extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWe
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);
const tabs = await this.findNativeElementOrElements('-ios predicate string', `name LIKE '*, Tab' AND visible = 1`, true);
if (tabs.length > 0) {
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
topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;

// 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);
}

topOffset += await this.getExtraNativeWebTapOffset();
Expand All @@ -124,23 +126,12 @@ extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWe
extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffset () {
let offset = 0;

// keep track of implicit wait, and set locally to 0
const implicitWaitMs = this.implicitWaitMs;
try {
this.setImplicitWait(0);

// 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() ?
IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET :
IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
} catch (ign) {
// no smart app banner found, so continue
}
} finally {
// return implicit wait to what it was
this.setImplicitWait(implicitWaitMs);
// try to see if there is an Smart App Banner
const banners = await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true);
if (banners.length > 0) {
offset += await this.getSafariIsIphone() ?
IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET :
IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
}

log.debug(`Additional native web tap offset computed: ${offset}`);
Expand Down Expand Up @@ -191,7 +182,8 @@ async function tapWebElementNatively (driver, atomsElement) {
extensions.nativeWebTap = async function nativeWebTap (el) {
const atomsElement = this.useAtomsElement(el);

if (await tapWebElementNatively(this, atomsElement)) {
// if strict native tap, do not try to do it with WDA directly
if (!(await this.settings.getSettings()).nativeWebTapStrict && await tapWebElementNatively(this, atomsElement)) {
return;
}
log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
Expand Down Expand Up @@ -223,17 +215,15 @@ extensions.translateWebCoords = async function translateWebCoords (coords) {
log.debug(`Translating coordinates (${JSON.stringify(coords)}) to web coordinates`);

// absolutize web coords
const implicitWaitMs = this.implicitWaitMs;
let webview;
try {
this.setImplicitWait(0);
webview = await retryInterval(5, 100,
async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false));
} finally {
this.setImplicitWait(implicitWaitMs);
}

let webview = await retryInterval(5, 100, async () => {
const webviews = await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', true);
if (webviews.length === 0) {
throw new Error(`No webviews found. Unable to translate web coordinates for native web tap`);
}
return webviews[0];
});
webview = util.unwrapElement(webview);

const rect = await this.proxyCommand(`/element/${webview}/rect`, 'GET');
const wvPos = {x: rect.x, y: rect.y};
const realDims = {w: rect.width, h: rect.height};
Expand Down Expand Up @@ -271,8 +261,9 @@ extensions.checkForAlert = async function checkForAlert () {
try {
await this.getAlert();
return true;
} catch (ign) {
} catch (err) {
// no alert found, so pass through and return false
log.debug(`No alert found: ${err.message}`);
}
return false;
};
Expand Down
5 changes: 4 additions & 1 deletion lib/desired-caps.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,10 @@ let desiredCapConstraints = _.defaults({
},
appPushTimeout: {
isNumber: true
}
},
nativeWebTapStrict: {
isBoolean: true
},
}, iosDesiredCapConstraints);

export { desiredCapConstraints, PLATFORM_NAME_IOS, PLATFORM_NAME_TVOS };
Expand Down
11 changes: 7 additions & 4 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const WDA_REAL_DEV_TUTORIAL_URL = 'https://github.com/appium/appium-xcuitest-dri
const WDA_STARTUP_RETRY_INTERVAL = 10000;
const DEFAULT_SETTINGS = {
nativeWebTap: false,
nativeWebTapStrict: false,
useJSONSource: false,
shouldUseCompactResponses: true,
elementResponseAttributes: 'type,label',
Expand Down Expand Up @@ -119,8 +120,6 @@ const MEMOIZED_FUNCTIONS = [
'getStatusBarHeight',
'getDevicePixelRatio',
'getScreenInfo',
'getSafariIsIphone',
'getSafariIsIphoneX',
];

class XCUITestDriver extends BaseDriver {
Expand Down Expand Up @@ -156,12 +155,12 @@ class XCUITestDriver extends BaseDriver {
}

async onSettingsUpdate (key, value) {
if (key !== 'nativeWebTap') {
if (key !== 'nativeWebTap' && key !== 'nativeWebTapStrict') {
return await this.proxyCommand('/appium/settings', 'POST', {
settings: {[key]: value}
});
}
this.opts.nativeWebTap = !!value;
this.opts[key] = !!value;
}

resetIos () {
Expand Down Expand Up @@ -221,6 +220,10 @@ class XCUITestDriver extends BaseDriver {
if (_.has(this.opts, 'nativeWebTap')) {
await this.updateSettings({nativeWebTap: this.opts.nativeWebTap});
}
// ensure we track nativeWebTapStrict capability as a setting as well
if (_.has(this.opts, 'nativeWebTapStrict')) {
await this.updateSettings({nativeWebTapStrict: this.opts.nativeWebTapStrict});
}
// ensure we track useJSONSource capability as a setting as well
if (_.has(this.opts, 'useJSONSource')) {
await this.updateSettings({useJSONSource: this.opts.useJSONSource});
Expand Down
Loading

0 comments on commit f2ef0a9

Please sign in to comment.