Skip to content

Commit

Permalink
feat: Control the visibility of the Tab Bar and Smart App Banner (#1345)
Browse files Browse the repository at this point in the history
  • Loading branch information
zcmgyu authored Oct 29, 2021
1 parent 8fa185d commit d4e4e93
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 20 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ keyboardAutocorrection | boolean | Changes the 'Auto-Correction' preference in _
keyboardPrediction | boolean | Changes the 'Predictive' preference in _Keyboards_ setting. Defaults to `false`.
nativeWebTap | boolean | See the description of the corresponding capability.
nativeWebTapStrict | boolean | See the description of the corresponding capability.
nativeWebTapTabBarVisibility | enum | Bypass finding whether the existence of the _**tab bar**_ before tapping on the element. It could make native web tap faster. If it's `visible`, tab bar offset will be added without checking the existence of the tab bar. It's `invisible`, the tab bar offset will be `zero`. If you want to leave Appium to check and measure the tab bar offset, unset or set `detect`. Only applicable if `nativeWebTap` and `nativeWebTapStrict` are enabled. Unset by default.
nativeWebTapSmartAppBannerVisibility | enum | The same as `nativeWebTapTabBarVisibility`, this keyword will bypass finding whether the existence of the _**smart app banner**_.
useJSONSource | boolean | See the description of the corresponding capability.

## Element Location
Expand Down
82 changes: 62 additions & 20 deletions lib/commands/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const ATOM_INITIAL_WAIT_MS = 1000;

const ON_OBSTRUCTING_ALERT_EVENT = 'alert';

const VISIBLE = 'visible';
const INVISIBLE = 'invisible';
const DETECT = 'detect';
const VISIBILITIES = [VISIBLE, INVISIBLE, DETECT];

const commands = {}, helpers = {}, extensions = {};

function isValidElementIdentifier (id) {
Expand Down Expand Up @@ -338,6 +343,21 @@ extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWe
let topOffset = 0;
let bottomOffset = 0;

// No need to check whether the Smart App Banner or Tab Bar is visible or not
// if already defined by nativeWebTapTabBarVisibility or nativeWebTapSmartAppBannerVisibility in settings.
const {
nativeWebTapTabBarVisibility,
nativeWebTapSmartAppBannerVisibility
} = await this.settings.getSettings();
let tabBarVisibility = _.lowerCase(nativeWebTapTabBarVisibility);
let bannerVisibility = _.lowerCase(nativeWebTapSmartAppBannerVisibility);
if (!VISIBILITIES.includes(tabBarVisibility)) {
tabBarVisibility = DETECT;
}
if (!VISIBILITIES.includes(bannerVisibility)) {
bannerVisibility = DETECT;
}

const isIphone = await this.getSafariIsIphone();
const isNotched = isIphone && await this.getSafariIsNotched();

Expand Down Expand Up @@ -374,29 +394,43 @@ extensions.getExtraTranslateWebCoordsOffset = async function getExtraTranslateWe
}

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
const tabs = await this.findNativeElementOrElements('-ios predicate string', `name LIKE '*, Tab' AND visible = 1`, true);
if (tabs.length > 0) {
if (tabBarVisibility === VISIBLE) {
topOffset += TAB_BAR_OFFSET;
} else if (tabBarVisibility === DETECT) {
// Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
// Assume that each tab bar is a WebView
const contextsAndViews = await this.getContextsAndViews();
const tabs = contextsAndViews.filter((ctx) => ctx.id.startsWith('WEBVIEW_'));

if (tabs.length > 1) {
log.debug(`Found ${tabs.length} tabs. Assuming the tab bar is visible`);
topOffset += TAB_BAR_OFFSET;
}
}
}
}

topOffset += await this.getExtraNativeWebTapOffset();
topOffset += await this.getExtraNativeWebTapOffset(isIphone, bannerVisibility);

wvPos.y += topOffset;
realDims.h -= (topOffset + bottomOffset);
};

extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffset () {
extensions.getExtraNativeWebTapOffset = async function getExtraNativeWebTapOffset (isIphone, bannerVisibility) {
let offset = 0;

// 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() ?
if (bannerVisibility === VISIBLE) {
offset += isIphone ?
IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET :
IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
} else if (bannerVisibility === DETECT) {
// 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 += isIphone ?
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 @@ -455,11 +489,17 @@ extensions.nativeWebTap = async function nativeWebTap (el) {

// `get_top_left_coordinates` returns the wrong value sometimes,
// unless we pre-call both of these functions before the actual calls
await this.executeAtom('get_size', [atomsElement]);
await this.executeAtom('get_top_left_coordinates', [atomsElement]);
await B.Promise.all([
this.executeAtom('get_size', [atomsElement]),
this.executeAtom('get_top_left_coordinates', [atomsElement]),
]);

const {width, height} = await this.executeAtom('get_size', [atomsElement]);
let {x, y} = await this.executeAtom('get_top_left_coordinates', [atomsElement]);
const [size, coordinates] = await B.Promise.all([
this.executeAtom('get_size', [atomsElement]),
this.executeAtom('get_top_left_coordinates', [atomsElement]),
]);
const {width, height} = size;
let {x, y} = coordinates;
x += width / 2;
y += height / 2;

Expand All @@ -480,13 +520,15 @@ extensions.translateWebCoords = async function translateWebCoords (coords) {
log.debug(`Translating coordinates (${JSON.stringify(coords)}) to web coordinates`);

// absolutize web coords
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];
});
let webview;
try {
webview = await retryInterval(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false));
} catch (ign) {}

if (!webview) {
throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
}

webview = util.unwrapElement(webview);

const rect = await this.proxyCommand(`/element/${webview}/rect`, 'GET');
Expand Down
45 changes: 45 additions & 0 deletions test/functional/web/safari-nativewebtap-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { retryInterval } from 'asyncbox';
import B from 'bluebird';
import Simctl from 'node-simctl';
import { util } from 'appium-support';
import { performance } from 'perf_hooks';

/**
* This test suite can be affected by two environment variables:
Expand Down Expand Up @@ -113,6 +114,28 @@ describe('Safari - coordinate conversion -', function () {
await spinTitleEquals(driver, PAGE_3_TITLE, SPIN_RETRIES);
});

it('should be able to bypass measuring the offset of banner', async function () {
await driver.updateSettings({
nativeWebTapStrict: true,
});

const start1 = performance.now();
await loadPage(driver, GUINEA_PIG_APP_BANNER_PAGE);
await driver.elementByLinkText(PAGE_3_LINK).click();
await spinTitleEquals(driver, PAGE_3_TITLE, SPIN_RETRIES);
const end1 = performance.now();
const durationWithoutIgnore = end1 - start1;

const start2 = performance.now();
await driver.updateSettings({ nativeWebTapSmartAppBannerVisibility: 'invisible' });
await loadPage(driver, GUINEA_PIG_APP_BANNER_PAGE);
await driver.elementByLinkText(PAGE_3_LINK).click();
await spinTitleEquals(driver, PAGE_3_TITLE, SPIN_RETRIES);
const end2 = performance.now();
const durationWithIgnore = end2 - start2;
durationWithIgnore.should.be.below(durationWithoutIgnore);
});

it('should be able to tap on an element after scrolling', async function () {
await loadPage(driver, GUINEA_PIG_SCROLLABLE_PAGE);
await driver.execute('mobile: scroll', {direction: 'down'});
Expand Down Expand Up @@ -176,6 +199,27 @@ describe('Safari - coordinate conversion -', function () {

await spinTitleEquals(driver, PAGE_3_TITLE, SPIN_RETRIES);
});

it('should be able to bypass measuring the offset', async function () {
await driver.updateSettings({
nativeWebTapStrict: true,
});

const start1 = performance.now();
await loadPage(driver, GUINEA_PIG_PAGE);
await driver.elementByLinkText(PAGE_3_LINK).click();
const end1 = performance.now();
const durationWithoutControlled = end1 - start1;

await driver.updateSettings({ nativeWebTapTabBarVisibility: 'visible' });
const start2 = performance.now();
await loadPage(driver, GUINEA_PIG_PAGE);
await driver.elementByLinkText(PAGE_3_LINK).click();
const end2 = performance.now();
const durationWithControlled = end2 - start2;
durationWithControlled.should.be.below(durationWithoutControlled);
});

it('should be able to tap on an element after scrolling', async function () {
await loadPage(driver, GUINEA_PIG_SCROLLABLE_PAGE);
await driver.execute('mobile: scroll', {direction: 'down'});
Expand All @@ -184,6 +228,7 @@ describe('Safari - coordinate conversion -', function () {

await spinTitleEquals(driver, PAGE_3_TITLE, SPIN_RETRIES);
});

it('should be able to tap on an element after scrolling, when the url bar is present', async function () {
await loadPage(driver, GUINEA_PIG_SCROLLABLE_PAGE);
await driver.execute('mobile: scroll', {direction: 'down'});
Expand Down

0 comments on commit d4e4e93

Please sign in to comment.