UNPKG

appium-remote-debugger

Version:
182 lines 7.54 kB
"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 base_driver_1 = require("@appium/base-driver"); const utils_1 = require("../utils"); const atoms_1 = require("../atoms"); const support_1 = require("@appium/support"); const asyncbox_1 = require("asyncbox"); const lodash_1 = __importDefault(require("lodash")); /* How many milliseconds to wait for webkit to return a response before timing out */ const RPC_RESPONSE_TIMEOUT_MS = 5000; /** * Execute a Selenium atom in Safari * @param {string} atom Name of Selenium atom (see atoms/ directory) * @param {any[]} args Arguments passed to the atom * @param {string[]} frames * @returns {Promise<string>} The result received from the atom */ async function executeAtom(atom, args = [], frames = []) { if (!this.rpcClient?.isConnected) { throw new Error('Remote debugger is not connected'); } logger_1.default.debug(`Executing atom '${atom}' with 'args=${JSON.stringify(args)}; frames=${frames}'`); const script = await (0, atoms_1.getScriptForAtom)(atom, args, frames); const value = await this.execute(script, true); logger_1.default.debug(`Received result for atom '${atom}' execution: ${lodash_1.default.truncate((0, utils_1.simpleStringify)(value), { length: utils_1.RESPONSE_LOG_LENGTH })}`); return value; } /** * @this {import('../remote-debugger').RemoteDebugger} * @param {string} atom * @param {any[]} [args] * @param {string[]} [frames] * @returns {Promise<any>} */ async function executeAtomAsync(atom, args = [], frames = []) { // helper to send directly to the web inspector const evaluate = async (method, opts) => { if (!this.rpcClient?.isConnected) { throw new Error('Remote debugger is not connected'); } return await this.rpcClient.send(method, Object.assign({ appIdKey: this.appIdKey, pageIdKey: this.pageIdKey, returnByValue: false, }, opts)); }; // first create a Promise on the page, saving the resolve/reject functions // as properties const promiseName = `appiumAsyncExecutePromise${support_1.util.uuidV4().replace(/-/g, '')}`; const script = `var res, rej; window.${promiseName} = new Promise(function (resolve, reject) { res = resolve; rej = reject; }); window.${promiseName}.resolve = res; window.${promiseName}.reject = rej; window.${promiseName};`; const obj = await evaluate('Runtime.evaluate', { expression: script, }); const promiseObjectId = obj.result.objectId; // execute the atom, calling back to the resolve function const asyncCallBack = `function (res) { window.${promiseName}.resolve(res); window.${promiseName}Value = res; }`; await this.execute(await (0, atoms_1.getScriptForAtom)(atom, args, frames, asyncCallBack)); // wait for the promise to be resolved let res; const subcommandTimeout = 1000; // timeout on individual commands try { res = await evaluate('Runtime.awaitPromise', { promiseObjectId, returnByValue: true, generatePreview: true, saveResult: true, }); } catch (err) { if (!err.message.includes(`'Runtime.awaitPromise' was not found`)) { throw err; } // awaitPromise is not always available, so simulate it with poll const retryWait = 100; const timeout = (args.length >= 3) ? args[2] : RPC_RESPONSE_TIMEOUT_MS; // if the timeout math turns up 0 retries, make sure it happens once const retries = parseInt(`${timeout / retryWait}`, 10) || 1; const timer = new support_1.timing.Timer().start(); logger_1.default.debug(`Waiting up to ${timeout}ms for async execute to finish`); res = await (0, asyncbox_1.retryInterval)(retries, retryWait, async () => { // the atom _will_ return, either because it finished or an error // including a timeout error const hasValue = await evaluate('Runtime.evaluate', { expression: `window.hasOwnProperty('${promiseName}Value');`, returnByValue: true, }); if (hasValue) { // we only put the property on `window` when the callback is called, // so if it is there, everything is done return await evaluate('Runtime.evaluate', { expression: `window.${promiseName}Value;`, returnByValue: true, }); } // throw a TimeoutError, or else it needs to be caught and re-thrown throw new base_driver_1.errors.TimeoutError(`Timed out waiting for asynchronous script ` + `result after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms'));`); }); } finally { try { // try to get rid of the promise await this.executeAtom('execute_script', [`delete window.${promiseName};`, [null, null], subcommandTimeout], frames); } catch (ign) { } } return (0, utils_1.convertResult)(res); } /** * @this {import('../remote-debugger').RemoteDebugger} * @param {string} command * @param {boolean} [override] * @returns {Promise<any>} */ async function execute(command, override) { // if the page is not loaded yet, wait for it if (this.pageLoading && !override) { logger_1.default.debug('Trying to execute but page is not loaded.'); await this.waitForDom(); } if (lodash_1.default.isNil(this.appIdKey)) { throw new Error('Missing parameter: appIdKey. Is the target web application still alive?'); } if (lodash_1.default.isNil(this.pageIdKey)) { throw new Error('Missing parameter: pageIdKey. Is the target web page still alive?'); } if (this.garbageCollectOnExecute) { await this.garbageCollect(); } logger_1.default.debug(`Sending javascript command: '${lodash_1.default.truncate(command, { length: 50 })}'`); if (!this.rpcClient?.isConnected) { throw new Error('Remote debugger is not connected'); } const res = await this.rpcClient.send('Runtime.evaluate', { expression: command, returnByValue: true, appIdKey: this.appIdKey, pageIdKey: this.pageIdKey, }); return (0, utils_1.convertResult)(res); } /** * @this {import('../remote-debugger').RemoteDebugger} * @param {string} objectId * @param {any} fn * @param {any[]} [args] */ async function callFunction(objectId, fn, args) { if (!this.rpcClient?.isConnected) { throw new Error('Remote debugger is not connected'); } (0, utils_1.checkParams)({ appIdKey: this.appIdKey, pageIdKey: this.pageIdKey }); if (this.garbageCollectOnExecute) { await this.garbageCollect(); } logger_1.default.debug('Calling javascript function'); const res = await this.rpcClient.send('Runtime.callFunctionOn', { objectId, functionDeclaration: fn, arguments: args, returnByValue: true, appIdKey: this.appIdKey, pageIdKey: this.pageIdKey, }); return (0, utils_1.convertResult)(res); } exports.default = { executeAtom, executeAtomAsync, execute, callFunction }; //# sourceMappingURL=execute.js.map