diff --git a/src/agent/browser-driver/create-safari-apple-script-driver.js b/src/agent/browser-driver/create-safari-apple-script-driver.js index 98039ea..ac83e12 100644 --- a/src/agent/browser-driver/create-safari-apple-script-driver.js +++ b/src/agent/browser-driver/create-safari-apple-script-driver.js @@ -1,7 +1,5 @@ import { execFile } from 'child_process'; - -/** Number of milliseconds to wait for document to be ready before giving up. */ -const DOCUMENT_READY_TIMEOUT = 2000; +import { timesOption } from '../../shared/times-option.js'; /** * @param {string} source @@ -60,7 +58,7 @@ export default async () => { async documentReady() { const start = Date.now(); - while (Date.now() - start < DOCUMENT_READY_TIMEOUT) { + while (Date.now() - start < timesOption.docReady) { const readyState = await evalJavaScript('document.readyState'); if (readyState === 'complete') { return; diff --git a/src/agent/cli.js b/src/agent/cli.js index bfdb09a..c476cca 100644 --- a/src/agent/cli.js +++ b/src/agent/cli.js @@ -12,6 +12,7 @@ import { iterateEmitter } from '../shared/iterate-emitter.js'; import { createRunner } from './create-test-runner.js'; import { agentMain } from './main.js'; import { AgentMessage, createAgentLogger } from './messages.js'; +import { timesArgs, timesOptionDescription } from '../shared/times-option.js'; /** @param {yargs} args */ export function buildAgentCliOptions(args = yargs) { @@ -76,6 +77,7 @@ export function buildAgentCliOptions(args = yargs) { choices: ['request', 'skip'], hidden: true, }, + times: timesOptionDescription, }) .showHidden('show-hidden'); } @@ -86,6 +88,7 @@ export function buildAgentCliOptions(args = yargs) { */ export function agentCliArgsFromOptionsMap(options) { const args = []; + args.push('--times', timesArgs()); for (const key of Object.keys(options)) { const value = options[key]; switch (key) { diff --git a/src/agent/driver-test-runner.js b/src/agent/driver-test-runner.js index 6c2c9fa..c9fd4c3 100644 --- a/src/agent/driver-test-runner.js +++ b/src/agent/driver-test-runner.js @@ -3,6 +3,7 @@ /// import { startJob } from '../shared/job.js'; +import { timesOption } from '../shared/times-option.js'; import { ATDriver, ATKey, webDriverCodePoints } from './at-driver.js'; import { AgentMessage } from './messages.js'; @@ -11,10 +12,6 @@ import { AgentMessage } from './messages.js'; * @module agent */ -const AFTER_NAVIGATION_DELAY = 1000; -const AFTER_KEYS_DELAY = 5000; -const RUN_TEST_SETUP_BUTTON_TIMEOUT = 1000; - export class DriverTestRunner { /** * @param {object} options @@ -49,10 +46,7 @@ export class DriverTestRunner { await this.browserDriver.documentReady(); try { - await this.browserDriver.clickWhenPresent( - '.button-run-test-setup', - RUN_TEST_SETUP_BUTTON_TIMEOUT - ); + await this.browserDriver.clickWhenPresent('.button-run-test-setup', timesOption.testSetup); } catch ({}) { await this.log(AgentMessage.NO_RUN_TEST_SETUP, { referencePage }); } @@ -71,15 +65,10 @@ export class DriverTestRunner { * @param {string} desiredResponse */ async pressKeysToToggleSetting(sequence, desiredResponse) { - // This timeout may be reached as many as two times for every test. - // Delays of over 500ms have been observed during local testing in a - // Windows virtual machine. - const MODE_SWITCH_SPEECH_TIMEOUT = 750; - let unknownCollected = ''; // there are 2 modes, so we will try pressing mode switch up to twice for (let triesRemain = 2; triesRemain > 0; triesRemain--) { - const speechResponse = await this._collectSpeech(MODE_SWITCH_SPEECH_TIMEOUT, () => + const speechResponse = await this._collectSpeech(timesOption.modeSwitch, () => this.sendKeys(sequence) ); while (speechResponse.length) { @@ -202,7 +191,7 @@ export class DriverTestRunner { const { value: validCommand, errors } = validateKeysFromCommand(command); if (validCommand) { - await this._collectSpeech(AFTER_NAVIGATION_DELAY, () => + await this._collectSpeech(timesOption.afterNav, () => this.openPage({ url: this._appendBaseUrl(test.target.referencePage), referencePage: test.target.referencePage, @@ -217,11 +206,11 @@ export class DriverTestRunner { await this.ensureMode(test.target.mode); } - const spokenOutput = await this._collectSpeech(AFTER_KEYS_DELAY, () => + const spokenOutput = await this._collectSpeech(timesOption.afterKeys, () => this.sendKeys(atKeysFromCommand(validCommand)) ); - await this._collectSpeech(AFTER_NAVIGATION_DELAY, async () => { + await this._collectSpeech(timesOption.afterNav, async () => { await this.log(AgentMessage.OPEN_PAGE, { url: 'about:blank' }); await this.browserDriver.navigate('about:blank'); }); diff --git a/src/host/cli-run-plan.js b/src/host/cli-run-plan.js index ee1b311..3e962c7 100644 --- a/src/host/cli-run-plan.js +++ b/src/host/cli-run-plan.js @@ -15,6 +15,7 @@ import { hostMain } from './main.js'; import { HostMessage, createHostLogger } from './messages.js'; import { plansFrom } from './plan-from.js'; import { HostServer } from './server.js'; +import { timesOptionDescription } from '../shared/times-option.js'; export const command = 'run-plan [plan-files..]'; @@ -156,6 +157,7 @@ export const builder = (args = yargs) => return { [name]: value }; }, }, + times: timesOptionDescription, }) .showHidden('show-hidden') .middleware(verboseMiddleware) diff --git a/src/shared/times-option.js b/src/shared/times-option.js new file mode 100644 index 0000000..7a4333a --- /dev/null +++ b/src/shared/times-option.js @@ -0,0 +1,42 @@ +// modeSwitch - After switching modes, delay this long to receive speech. +// afterNav - Delay this long after a navigation to receive and clear speech. +// afterKeys - After pressing a key, delay this long to receive and record speech. +// testSetup - Delay this long after pressing the test setup to receive and clear speech. +// docReady - Wait this long for document to be ready (safari only). + +export const timesOption = { + afterNav: 1000, + afterKeys: 5000, + testSetup: 1000, + modeSwitch: 750, + docReady: 2000, +}; + +const timesDefaults = { ...timesOption }; + +export function timesArgs(opts = timesOption) { + return Object.entries(opts) + .map(([key, value]) => `${key}:${value}`) + .join(','); +} + +export const timesOptionDescription = { + // hidden by default because it's a really long one + hidden: true, + default: timesArgs(timesDefaults), + describe: 'Configure timeout and delays', + coerce(arg) { + if (!arg) return; + const parts = arg.split(/,/g); + for (const part of parts) { + const match = part.match(/^([^:]+):(\d+)$/); + if (!match) { + throw new Error(`Error parsing times "${part}"`); + } + if (!timesOption[match[1]]) { + throw new Error(`Unknown times param "${match[1]}"`); + } + timesOption[match[1]] = parseInt(match[2], 10); + } + }, +};