appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
221 lines • 8.49 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = __importDefault(require("../logger"));
const utils_1 = require("../utils");
const events_1 = __importDefault(require("./events"));
const support_1 = require("@appium/support");
const lodash_1 = __importDefault(require("lodash"));
const bluebird_1 = __importDefault(require("bluebird"));
/**
* @typedef {(() => Promise<any>|void)|undefined} TPageLoadVerifyHook
*/
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @returns {void}
*/
function frameDetached() {
this.emit(events_1.default.EVENT_FRAMES_DETACHED);
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @param {timing.Timer?} startPageLoadTimer
* @param {TPageLoadVerifyHook} [pageLoadVerifyHook]
* @returns {Promise<void>}
*/
async function pageLoad(startPageLoadTimer, pageLoadVerifyHook = lodash_1.default.noop) {
const timeoutMs = 500;
if (!lodash_1.default.isFunction(startPageLoadTimer?.getDuration)) {
logger_1.default.debug(`Page load timer not a timer. Creating new timer`);
startPageLoadTimer = new support_1.timing.Timer().start();
}
logger_1.default.debug('Page loaded, verifying whether ready');
this.pageLoading = true;
const verify = async () => {
this.pageLoadDelay = support_1.util.cancellableDelay(timeoutMs);
try {
await this.pageLoadDelay;
}
catch (err) {
if (err instanceof bluebird_1.default.CancellationError) {
// if the promise has been cancelled
// we want to skip checking the readiness
return;
}
}
// we can get this called in the middle of trying to find a new app
if (!this.appIdKey) {
logger_1.default.debug('Not connected to an application. Ignoring page load');
return;
}
if (lodash_1.default.isFunction(pageLoadVerifyHook)) {
await pageLoadVerifyHook();
}
// if we are ready, or we've spend too much time on this
// @ts-ignore startPageLoadTimer is defined here
const elapsedMs = startPageLoadTimer.getDuration().asMilliSeconds;
if (await this.checkPageIsReady() || (this.pageLoadMs > 0 && elapsedMs > this.pageLoadMs)) {
logger_1.default.debug('Page is ready');
this.pageLoading = false;
}
else {
logger_1.default.debug('Page was not ready, retrying');
await verify();
}
};
try {
await verify();
}
finally {
// @ts-ignore startPageLoadTimer is defined here
logger_1.default.debug(`Page load completed in ${startPageLoadTimer.getDuration().asMilliSeconds.toFixed(0)}ms`);
this.pageLoading = false;
}
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @returns {void}
*/
function cancelPageLoad() {
logger_1.default.debug('Unregistering from page readiness notifications');
this.pageLoading = false;
if (this.pageLoadDelay) {
this.pageLoadDelay.cancel();
}
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @returns {Promise<void>}
*/
async function pageUnload() {
logger_1.default.debug('Page unloading');
await this.waitForDom();
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @param {timing.Timer|null|undefined} startPageLoadTimer
* @param {TPageLoadVerifyHook} [pageLoadVerifyHook]
* @returns {Promise<void>}
*/
async function waitForDom(startPageLoadTimer, pageLoadVerifyHook) {
logger_1.default.debug('Waiting for dom...');
await this.pageLoad(startPageLoadTimer, pageLoadVerifyHook);
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @returns {Promise<boolean>}
*/
async function checkPageIsReady() {
(0, utils_1.checkParams)({ appIdKey: this.appIdKey });
logger_1.default.debug('Checking document readyState');
const readyCmd = 'document.readyState;';
let readyState = 'loading';
try {
readyState = await bluebird_1.default.resolve(this.execute(readyCmd, true)).timeout(this.pageReadyTimeout);
}
catch (err) {
if (!(err instanceof bluebird_1.default.TimeoutError)) {
throw err;
}
logger_1.default.debug(`Page readiness check timed out after ${this.pageReadyTimeout}ms`);
return false;
}
logger_1.default.debug(`Document readyState is '${readyState}'`);
return readyState === 'complete';
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @param {string} url
* @param {TPageLoadVerifyHook} [pageLoadVerifyHook]
* @returns {Promise<void>}
*/
async function navToUrl(url, pageLoadVerifyHook) {
(0, utils_1.checkParams)({ appIdKey: this.appIdKey, pageIdKey: this.pageIdKey });
if (!this.rpcClient) {
throw new Error('rpcClient is undefined. Is the debugger connected?');
}
this._navigatingToPage = true;
try {
logger_1.default.debug(`Navigating to new URL: '${url}'`);
// begin listening for frame navigation event, which will be waited for later
const waitForFramePromise = this.waitForFrameNavigated();
await this.rpcClient.send('Page.navigate', {
url,
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
});
if (!this.useNewSafari) {
// a small pause for the browser to catch up
await bluebird_1.default.delay(1000);
}
// wait until the page has been navigated
await waitForFramePromise;
await this.waitForDom(new support_1.timing.Timer().start(), pageLoadVerifyHook);
// enable console logging, so we get the events (otherwise we only
// get notified when navigating to a local page
await this.rpcClient.send('Console.enable', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
});
}
finally {
this._navigatingToPage = false;
}
}
/**
* @this {import('../remote-debugger').RemoteDebugger}
* @returns {Promise<any>}
*/
async function waitForFrameNavigated() {
let navEventListener;
return await new bluebird_1.default(async (resolve) => {
logger_1.default.debug('Waiting for frame navigated message...');
if (!this.rpcClient) {
throw new Error('rpcClient is undefined. Is the debugger connected?');
}
const start = new support_1.timing.Timer().start();
// add a handler for the `Page.frameNavigated` message
// from the remote debugger
navEventListener = (err, value) => {
logger_1.default.debug(`Frame navigated in ${start.getDuration().asMilliSeconds.toFixed(0)}ms from: ${value}`);
if (!this.allowNavigationWithoutReload && !this.pageLoading) {
resolve(value);
}
else {
logger_1.default.debug('Frame navigated but we were warned about it, not ' +
'considering page state unloaded');
this.allowNavigationWithoutReload = false;
}
if (this.navigationDelay) {
this.navigationDelay.cancel();
}
};
this.rpcClient.once('Page.frameNavigated', navEventListener);
// timeout, in case remote debugger doesn't respond,
// or takes a long time
if (!this.useNewSafari || this.pageLoadMs >= 0) {
// use pageLoadMs, or a small amount of time
const timeout = this.useNewSafari ? this.pageLoadMs : 500;
this.navigationDelay = support_1.util.cancellableDelay(timeout);
try {
await this.navigationDelay;
navEventListener(null, `${timeout}ms timeout`);
}
catch (err) {
// nothing to do: we only get here if the remote debugger
// already notified of frame navigation, and the delay
// was cancelled
}
}
}).finally(() => {
if (navEventListener) {
this.rpcClient?.off('Page.frameNavigated', navEventListener);
}
});
}
exports.default = {
frameDetached, pageLoad, cancelPageLoad, pageUnload, waitForDom, checkPageIsReady, navToUrl, waitForFrameNavigated
};
//# sourceMappingURL=navigate.js.map