UNPKG

appium-remote-debugger

Version:
277 lines 11.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_PAGE_READINESS_TIMEOUT_MS = void 0; exports.frameDetached = frameDetached; exports.cancelPageLoad = cancelPageLoad; exports.isPageLoadingCompleted = isPageLoadingCompleted; exports.waitForDom = waitForDom; exports.checkPageIsReady = checkPageIsReady; exports.navToUrl = navToUrl; const utils_1 = require("../utils"); const events_1 = require("./events"); const support_1 = require("@appium/support"); const lodash_1 = __importDefault(require("lodash")); const bluebird_1 = __importStar(require("bluebird")); const property_accessors_1 = require("./property-accessors"); exports.DEFAULT_PAGE_READINESS_TIMEOUT_MS = 20 * 1000; const PAGE_READINESS_CHECK_INTERVAL_MS = 50; /** * pageLoadStrategy in WebDriver definitions. */ const PAGE_LOAD_STRATEGY = Object.freeze({ EAGER: 'eager', NONE: 'none', NORMAL: 'normal', }); /** * Emits a frame detached event when a frame is detached from the page. * This is typically called by the RPC client when receiving a Page.frameDetached event. */ function frameDetached() { this.emit(events_1.events.EVENT_FRAMES_DETACHED); } /** * Cancels the current page load operation by unregistering from page readiness * notifications and canceling any pending page load delay. */ function cancelPageLoad() { this.log.debug('Unregistering from page readiness notifications'); (0, property_accessors_1.setPageLoading)(this, false); (0, property_accessors_1.getPageLoadDelay)(this)?.cancel(); } /** * Determines if the current readyState indicates that page loading is completed * based on the configured page load strategy. * * @param readyState - The document readyState value ('loading', 'interactive', or 'complete'). * @returns True if the page load is considered complete for the current strategy: * - 'eager': returns true when readyState is not 'loading' * - 'none': always returns true * - 'normal' (default): returns true only when readyState is 'complete' */ function isPageLoadingCompleted(readyState) { const pageLoadStrategy = lodash_1.default.toLower((0, property_accessors_1.getPageLoadStartegy)(this)); switch (pageLoadStrategy) { case PAGE_LOAD_STRATEGY.EAGER: // This could include 'interactive' or 'complete' return readyState !== 'loading'; case PAGE_LOAD_STRATEGY.NONE: return true; case PAGE_LOAD_STRATEGY.NORMAL: default: return readyState === 'complete'; } } /** * Waits for the DOM to be ready by periodically checking the page readiness state. * Uses exponential backoff for retry intervals and respects the configured page load * strategy and timeout settings. * * @param startPageLoadTimer - Optional timer instance to use for tracking elapsed time. * If not provided, a new timer will be created and started. */ async function waitForDom(startPageLoadTimer) { const readinessTimeoutMs = this.pageLoadMs; this.log.debug(`Waiting up to ${readinessTimeoutMs}ms for the page to be ready`); const timer = startPageLoadTimer ?? new support_1.timing.Timer().start(); let isPageLoading = true; (0, property_accessors_1.setPageLoading)(this, true); (0, property_accessors_1.setPageLoadDelay)(this, support_1.util.cancellableDelay(readinessTimeoutMs)); const pageReadinessPromise = bluebird_1.default.resolve((async () => { let retry = 0; while (isPageLoading) { // if we are ready, or we've spend too much time on this const elapsedMs = timer.getDuration().asMilliSeconds; // exponential retry const intervalMs = Math.min(PAGE_READINESS_CHECK_INTERVAL_MS * Math.pow(2, retry), readinessTimeoutMs - elapsedMs); await bluebird_1.default.delay(intervalMs); // we can get this called in the middle of trying to find a new app if (!(0, property_accessors_1.getAppIdKey)(this)) { this.log.debug('Not connected to an application. Ignoring page readiess check'); return; } if (!isPageLoading) { return; } const maxWaitMs = (readinessTimeoutMs - elapsedMs) * 0.95; if (await this.checkPageIsReady(maxWaitMs)) { if (isPageLoading) { this.log.debug(`Page is ready in ${elapsedMs}ms`); isPageLoading = false; } return; } if (elapsedMs > readinessTimeoutMs) { this.log.info(`Timed out after ${readinessTimeoutMs}ms of waiting for the page readiness. Continuing anyway`); isPageLoading = false; return; } retry++; } })()); const cancellationPromise = bluebird_1.default.resolve((async () => { try { await (0, property_accessors_1.getPageLoadDelay)(this); } catch { } })()); try { await bluebird_1.default.any([cancellationPromise, pageReadinessPromise]); } finally { isPageLoading = false; (0, property_accessors_1.setPageLoading)(this, false); (0, property_accessors_1.setPageLoadDelay)(this, bluebird_1.default.resolve()); } } /** * Checks if the current page is ready by executing a JavaScript command to * retrieve the document readyState and evaluating it against the page load strategy. * * @param timeoutMs - Optional timeout in milliseconds for the readyState check. * If not provided, uses the configured page ready timeout. * @returns A promise that resolves to true if the page is ready according to * the page load strategy, false otherwise or if the check times out. */ async function checkPageIsReady(timeoutMs) { const readyCmd = 'document.readyState;'; const actualTimeoutMs = timeoutMs ?? (0, property_accessors_1.getPageReadyTimeout)(this); try { const readyState = await bluebird_1.default.resolve(this.execute(readyCmd)).timeout(actualTimeoutMs); this.log.debug(JSON.stringify({ readyState, pageLoadStrategy: (0, property_accessors_1.getPageLoadStartegy)(this) ?? PAGE_LOAD_STRATEGY.NORMAL, })); return this.isPageLoadingCompleted(readyState); } catch (err) { if (err instanceof bluebird_1.TimeoutError) { this.log.debug(`Page readiness check timed out after ${actualTimeoutMs}ms`); } else { this.log.warn(`Page readiness check has failed. Original error: ${err.message}`); } return false; } } /** * Navigates to a new URL and waits for the page to be ready. * Validates the URL format, waits for the page to be available, sets window.location.href * via Runtime.evaluate (Page.navigate was removed from WebKit Inspector protocol), and * monitors for the Page.loadEventFired event or timeout. * * @param url - The URL to navigate to. Must be a valid URL format. * @throws TypeError if the provided URL is not a valid URL format. */ async function navToUrl(url) { const { appIdKey, pageIdKey } = (0, utils_1.checkParams)({ appIdKey: (0, property_accessors_1.getAppIdKey)(this), pageIdKey: (0, property_accessors_1.getPageIdKey)(this), }); const rpcClient = this.requireRpcClient(); try { new URL(url); } catch { throw new TypeError(`'${url}' is not a valid URL`); } this.log.debug(`Navigating to new URL: '${url}'`); (0, property_accessors_1.setNavigatingToPage)(this, true); await rpcClient.waitForPage(appIdKey, pageIdKey); const readinessTimeoutMs = this.pageLoadMs; let onPageLoaded; let onPageLoadedTimeout; (0, property_accessors_1.setPageLoadDelay)(this, support_1.util.cancellableDelay(readinessTimeoutMs)); (0, property_accessors_1.setPageLoading)(this, true); let isPageLoading = true; const start = new support_1.timing.Timer().start(); const pageReadinessPromise = new bluebird_1.default((resolve) => { onPageLoadedTimeout = setTimeout(() => { if (isPageLoading) { isPageLoading = false; this.log.info(`Timed out after ${start.getDuration().asMilliSeconds.toFixed(0)}ms of waiting ` + `for the ${url} page readiness. Continuing anyway`); } return resolve(); }, readinessTimeoutMs); onPageLoaded = () => { if (isPageLoading) { isPageLoading = false; this.log.debug(`The page ${url} is ready in ${start.getDuration().asMilliSeconds.toFixed(0)}ms`); } if (onPageLoadedTimeout) { clearTimeout(onPageLoadedTimeout); onPageLoadedTimeout = null; } return resolve(); }; // https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-loadEventFired rpcClient.once('Page.loadEventFired', onPageLoaded); // Page.navigate was removed from the WebKit Inspector protocol since iOS 26.4 // See https://github.com/appium/appium/issues/21976 rpcClient.send('Runtime.evaluate', { expression: `window.location.href = ${JSON.stringify(url)};`, appIdKey, pageIdKey, }); }); const cancellationPromise = bluebird_1.default.resolve((async () => { try { await (0, property_accessors_1.getPageLoadDelay)(this); } catch { } })()); try { await bluebird_1.default.any([cancellationPromise, pageReadinessPromise]); } finally { (0, property_accessors_1.setPageLoading)(this, false); isPageLoading = false; (0, property_accessors_1.setNavigatingToPage)(this, false); (0, property_accessors_1.setPageLoadDelay)(this, bluebird_1.default.resolve()); if (onPageLoadedTimeout && pageReadinessPromise.isFulfilled()) { clearTimeout(onPageLoadedTimeout); onPageLoadedTimeout = null; } if (onPageLoaded) { rpcClient.off('Page.loadEventFired', onPageLoaded); } } } //# sourceMappingURL=navigate.js.map