Skip to content

Commit

Permalink
fix: Start page status monitoring on page change event as well (#402)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Aug 8, 2024
1 parent 414f1b5 commit 8f71d19
Showing 1 changed file with 67 additions and 23 deletions.
90 changes: 67 additions & 23 deletions lib/mixins/navigate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { checkParams } from '../utils';
import { checkParams, pageArrayFromDict } from '../utils';
import events from './events';
import { timing, util } from '@appium/support';
import _ from 'lodash';
Expand Down Expand Up @@ -173,10 +173,9 @@ export async function checkPageIsReady (timeoutMs) {
* @returns {Promise<void>}
*/
export async function navToUrl (url) {
checkParams({
appIdKey: getAppIdKey(this),
pageIdKey: getPageIdKey(this),
});
const appIdKey = getAppIdKey(this);
const pageIdKey = getPageIdKey(this);
checkParams({appIdKey, pageIdKey});
const rpcClient = this.requireRpcClient();

try {
Expand All @@ -191,17 +190,37 @@ export async function navToUrl (url) {
/** @type {(() => void)|undefined} */
let onPageLoaded;
/** @type {(() => void)|undefined} */
let onPageChanged;
/** @type {(() => void)|undefined} */
let onTargetProvisioned;
/** @type {NodeJS.Timeout|undefined|null} */
let onPageLoadedTimeout;
setPageLoadDelay(this, util.cancellableDelay(readinessTimeoutMs));
setPageLoading(this, true);
let isPageLoading = true;
let didPageFinishLoad = false;
/** @type {Promise<void>|null} */
let pageReadinessCheckPromise = null;
const start = new timing.Timer().start();

/** @type {B<void>} */
const pageReadinessPromise = new B((resolve) => {
const performPageReadinessCheck = async () => {
while (isPageLoading) {
const pageReadyCheckStart = new timing.Timer().start();
try {
const isReady = await this.checkPageIsReady(PAGE_READINESS_JS_MIN_CHECK_INTERVAL_MS);
if (isReady && isPageLoading && onPageLoaded) {
return onPageLoaded();
}
} catch (ign) {}
const msLeft = PAGE_READINESS_JS_MIN_CHECK_INTERVAL_MS - pageReadyCheckStart.getDuration().asMilliSeconds;
if (msLeft > 0 && isPageLoading) {
await B.delay(msLeft);
}
}
};

onPageLoadedTimeout = setTimeout(() => {
if (isPageLoading) {
isPageLoading = false;
Expand All @@ -225,33 +244,55 @@ export async function navToUrl (url) {
didPageFinishLoad = true;
return resolve();
};

// Sometimes it could be observed that we do not receive
// any events for target provisioning while navigating to a new page,
// but only events related to the page change.
// So lets just start the monitoring loop as soon as any of these events arrives
// for the target page.
onPageChanged = async (
/** @type {Error|null} */ err,
/** @type {string} */ _appIdKey,
/** @type {import("@appium/types").StringRecord} */ pageDict
) => {
if (_appIdKey !== appIdKey) {
return;
}

/** @type {import('../types').Page|undefined} */
const targetPage = pageArrayFromDict(pageDict)
.find(({id}) => parseInt(`${id}`, 10) === parseInt(`${pageIdKey}`, 10));
if (targetPage?.url === url) {
this.log.debug(`The page ${targetPage.id} got a new URL ${url}`);
if (pageReadinessCheckPromise) {
this.log.debug('Page readiness monitoring is already running');
} else {
this.log.debug('Monitoring its readiness');
pageReadinessCheckPromise = performPageReadinessCheck();
await pageReadinessCheckPromise;
}
}
};
rpcClient.on('_rpc_forwardGetListing:', onPageChanged);

// https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-loadEventFired
rpcClient.once('Page.loadEventFired', onPageLoaded);
// Pages that have no proper DOM structure do not fire the `Page.loadEventFired` event
// so we rely on the very first event after target change, which is `onTargetProvisioned`
// and start sending `document.readyState` requests until we either succeed or
// another event/timeout happens
onTargetProvisioned = async () => {
while (isPageLoading) {
const pageReadyCheckStart = new timing.Timer().start();
try {
const isReady = await this.checkPageIsReady(PAGE_READINESS_JS_MIN_CHECK_INTERVAL_MS);
if (isReady && isPageLoading && onPageLoaded) {
return onPageLoaded();
}
} catch (ign) {}
const msLeft = PAGE_READINESS_JS_MIN_CHECK_INTERVAL_MS - pageReadyCheckStart.getDuration().asMilliSeconds;
if (msLeft > 0 && isPageLoading) {
await B.delay(msLeft);
}
this.log.debug('The page target has been provisioned');
if (pageReadinessCheckPromise) {
this.log.debug('Page readiness monitoring is already running');
} else {
this.log.debug('Monitoring its readiness');
pageReadinessCheckPromise = performPageReadinessCheck();
await pageReadinessCheckPromise;
}
};
rpcClient.targetSubscriptions.once(rpcConstants.ON_TARGET_PROVISIONED_EVENT, onTargetProvisioned);

rpcClient.send('Page.navigate', {
url,
appIdKey: getAppIdKey(this),
pageIdKey: getPageIdKey(this),
appIdKey,
pageIdKey,
});
});
/** @type {B<void>} */
Expand All @@ -278,6 +319,9 @@ export async function navToUrl (url) {
if (onPageLoaded) {
rpcClient.off('Page.loadEventFired', onPageLoaded);
}
if (onPageChanged) {
rpcClient.off('_rpc_forwardGetListing:', onPageChanged);
}
}

// enable console logging, so we get the events (otherwise we only
Expand Down

0 comments on commit 8f71d19

Please sign in to comment.