Skip to content

Commit 9ef800c

Browse files
authored
configurable timeout and delay values (w3c#61)
* configurable timeout and delay values * types draft * individual options for each time * more restrictive types for the helper functions * prefer lowercase string and number type (seems to be tsc standard) * remove makeSnakeCasedKey function * pass timesOption config to modules instead of stateful module * fix jsdoc
1 parent 21230f6 commit 9ef800c

10 files changed

+153
-23
lines changed

src/agent/browser-driver/create-safari-apple-script-driver.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { execFile } from 'child_process';
22

3-
/** Number of milliseconds to wait for document to be ready before giving up. */
4-
const DOCUMENT_READY_TIMEOUT = 2000;
5-
63
/**
74
* @param {string} source
85
* @returns {Promise<string>}
@@ -43,7 +40,10 @@ const evalJavaScript = source => {
4340
end tell`);
4441
};
4542

46-
export default async () => {
43+
/**
44+
* @param {AriaATCIShared.timesOption} timesOption
45+
*/
46+
export default async timesOption => {
4747
await execScript(`tell application "Safari"
4848
if documents = {} then make new document
4949
activate
@@ -60,7 +60,7 @@ export default async () => {
6060
async documentReady() {
6161
const start = Date.now();
6262

63-
while (Date.now() - start < DOCUMENT_READY_TIMEOUT) {
63+
while (Date.now() - start < timesOption.docReady) {
6464
const readyState = await evalJavaScript('document.readyState');
6565
if (readyState === 'complete') {
6666
return;

src/agent/browser-driver/create.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import createSafariAppleScriptDriver from './create-safari-apple-script-driver.j
66
* @param {{toString: function(): string}} options.url
77
* @param {AriaATCIAgent.Browser} [options.browser]
88
* @param {Promise<void>} options.abortSignal
9+
* @param {AriaATCIShared.timesOption} options.timesOption
910
*
1011
* @returns {Promise<BrowserDriver>}
1112
*/
12-
export async function createBrowserDriver({ url, browser = 'firefox', abortSignal }) {
13+
export async function createBrowserDriver({ url, browser = 'firefox', abortSignal, timesOption }) {
1314
const driver =
1415
browser === 'safari'
15-
? await createSafariAppleScriptDriver()
16+
? await createSafariAppleScriptDriver(timesOption)
1617
: await createWebDriver(browser, url.toString());
1718
abortSignal.then(() => driver.quit());
1819
return driver;

src/agent/cli.js

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { iterateEmitter } from '../shared/iterate-emitter.js';
1212
import { createRunner } from './create-test-runner.js';
1313
import { agentMain } from './main.js';
1414
import { AgentMessage, createAgentLogger } from './messages.js';
15+
import { getTimesOption, timesArgs, timesOptionsConfig } from '../shared/times-option.js';
1516

1617
/** @param {yargs} args */
1718
export function buildAgentCliOptions(args = yargs) {
@@ -76,6 +77,7 @@ export function buildAgentCliOptions(args = yargs) {
7677
choices: ['request', 'skip'],
7778
hidden: true,
7879
},
80+
...timesOptionsConfig,
7981
})
8082
.showHidden('show-hidden');
8183
}
@@ -128,6 +130,9 @@ export function agentCliArgsFromOptionsMap(options) {
128130
case 'mockOpenPage':
129131
args.push(`--mock-open-page=${value}`);
130132
break;
133+
case 'timesOption':
134+
args.push(...timesArgs(value));
135+
break;
131136
default:
132137
throw new Error(`unknown agent cli argument ${key}`);
133138
}
@@ -149,6 +154,7 @@ export function pickAgentCliOptions({
149154
atDriverUrl,
150155
mock,
151156
mockOpenPage,
157+
timesOption,
152158
}) {
153159
return {
154160
...(debug === undefined ? {} : { debug }),
@@ -160,6 +166,7 @@ export function pickAgentCliOptions({
160166
...(atDriverUrl === undefined ? {} : { atDriverUrl }),
161167
...(mock === undefined ? {} : { mock }),
162168
...(mockOpenPage === undefined ? {} : { mockOpenPage }),
169+
timesOption,
163170
};
164171
}
165172

@@ -245,6 +252,7 @@ async function agentRunnerMiddleware(argv) {
245252
webDriverBrowser: argv.webDriverBrowser,
246253
atDriverUrl: argv.atDriverUrl,
247254
abortSignal: argv.abortSignal,
255+
timesOption: getTimesOption(argv),
248256
});
249257
}
250258

src/agent/create-test-runner.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { AgentMessage } from './messages.js';
1919
* @param {AriaATCIAgent.Log} options.log
2020
* @param {AriaATCIAgent.MockOptions} [options.mock]
2121
* @param {AriaATCIAgent.Browser} [options.webDriverBrowser]
22+
* @param {AriaATCIShared.timesOption} options.timesOption
2223
* @param {{toString: function(): string}} options.webDriverUrl
2324
* @returns {Promise<AriaATCIAgent.TestRunner>}
2425
*/
@@ -30,11 +31,13 @@ export async function createRunner(options) {
3031
return new MockTestRunner({ mock: options.mock, ...options });
3132
}
3233
await new Promise(resolve => setTimeout(resolve, 1000));
34+
const { timesOption } = options;
3335
const [browserDriver, atDriver] = await Promise.all([
3436
createBrowserDriver({
3537
url: options.webDriverUrl,
3638
browser: options.webDriverBrowser,
3739
abortSignal: options.abortSignal,
40+
timesOption,
3841
}).catch(cause => {
3942
throw new Error('Error initializing browser driver', { cause });
4043
}),
@@ -46,5 +49,5 @@ export async function createRunner(options) {
4649
throw new Error('Error connecting to at-driver', { cause });
4750
}),
4851
]);
49-
return new DriverTestRunner({ ...options, browserDriver, atDriver });
52+
return new DriverTestRunner({ ...options, browserDriver, atDriver, timesOption });
5053
}

src/agent/driver-test-runner.js

+8-15
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,22 @@ import { AgentMessage } from './messages.js';
1111
* @module agent
1212
*/
1313

14-
const AFTER_NAVIGATION_DELAY = 1000;
15-
const AFTER_KEYS_DELAY = 5000;
16-
const RUN_TEST_SETUP_BUTTON_TIMEOUT = 1000;
17-
1814
export class DriverTestRunner {
1915
/**
2016
* @param {object} options
2117
* @param {AriaATCIShared.BaseURL} options.baseUrl
2218
* @param {AriaATCIAgent.Log} options.log
2319
* @param {BrowserDriver} options.browserDriver
2420
* @param {ATDriver} options.atDriver
21+
* @param {AriaATCIShared.timesOption} options.timesOption
2522
*/
26-
constructor({ baseUrl, log, browserDriver, atDriver }) {
23+
constructor({ baseUrl, log, browserDriver, atDriver, timesOption }) {
2724
this.baseUrl = baseUrl;
2825
this.log = log;
2926
this.browserDriver = browserDriver;
3027
this.atDriver = atDriver;
3128
this.collectedCapabilities = this.getCapabilities();
29+
this.timesOption = timesOption;
3230
}
3331

3432
async getCapabilities() {
@@ -51,7 +49,7 @@ export class DriverTestRunner {
5149
try {
5250
await this.browserDriver.clickWhenPresent(
5351
'.button-run-test-setup',
54-
RUN_TEST_SETUP_BUTTON_TIMEOUT
52+
this.timesOption.testSetup
5553
);
5654
} catch ({}) {
5755
await this.log(AgentMessage.NO_RUN_TEST_SETUP, { referencePage });
@@ -71,15 +69,10 @@ export class DriverTestRunner {
7169
* @param {string} desiredResponse
7270
*/
7371
async pressKeysToToggleSetting(sequence, desiredResponse) {
74-
// This timeout may be reached as many as two times for every test.
75-
// Delays of over 500ms have been observed during local testing in a
76-
// Windows virtual machine.
77-
const MODE_SWITCH_SPEECH_TIMEOUT = 750;
78-
7972
let unknownCollected = '';
8073
// there are 2 modes, so we will try pressing mode switch up to twice
8174
for (let triesRemain = 2; triesRemain > 0; triesRemain--) {
82-
const speechResponse = await this._collectSpeech(MODE_SWITCH_SPEECH_TIMEOUT, () =>
75+
const speechResponse = await this._collectSpeech(this.timesOption.modeSwitch, () =>
8376
this.sendKeys(sequence)
8477
);
8578
while (speechResponse.length) {
@@ -202,7 +195,7 @@ export class DriverTestRunner {
202195
const { value: validCommand, errors } = validateKeysFromCommand(command);
203196

204197
if (validCommand) {
205-
await this._collectSpeech(AFTER_NAVIGATION_DELAY, () =>
198+
await this._collectSpeech(this.timesOption.afterNav, () =>
206199
this.openPage({
207200
url: this._appendBaseUrl(test.target.referencePage),
208201
referencePage: test.target.referencePage,
@@ -217,11 +210,11 @@ export class DriverTestRunner {
217210
await this.ensureMode(test.target.mode);
218211
}
219212

220-
const spokenOutput = await this._collectSpeech(AFTER_KEYS_DELAY, () =>
213+
const spokenOutput = await this._collectSpeech(this.timesOption.afterKeys, () =>
221214
this.sendKeys(atKeysFromCommand(validCommand))
222215
);
223216

224-
await this._collectSpeech(AFTER_NAVIGATION_DELAY, async () => {
217+
await this._collectSpeech(this.timesOption.afterNav, async () => {
225218
await this.log(AgentMessage.OPEN_PAGE, { url: 'about:blank' });
226219
await this.browserDriver.navigate('about:blank');
227220
});

src/agent/types.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @property {AriaATCIShared.BaseURL} [webDriverUrl]
5858
* @property {AriaATCIAgent.Browser} [webDriverBrowser]
5959
* @property {AriaATCIShared.BaseURL} [atDriverUrl]
60+
* @property {AriaATCIShared.timesOption} [timesOption]
6061
*/
6162

6263
/**

src/host/agent.js

+1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class AgentDeveloperProtocol extends AgentProtocol {
319319
atDriverUrl: options.atDriverUrl,
320320
webDriverBrowser: options.webDriverBrowser,
321321
webDriverUrl: options.webDriverUrl,
322+
timesOption: options.timesOption,
322323
}),
323324
log,
324325
tests: iterateEmitter(this._testEmitter, 'message', 'stop'),

src/host/cli-run-plan.js

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { hostMain } from './main.js';
1515
import { HostMessage, createHostLogger } from './messages.js';
1616
import { plansFrom } from './plan-from.js';
1717
import { HostServer } from './server.js';
18+
import { getTimesOption, timesOptionsConfig } from '../shared/times-option.js';
1819

1920
export const command = 'run-plan [plan-files..]';
2021

@@ -156,6 +157,7 @@ export const builder = (args = yargs) =>
156157
return { [name]: value };
157158
},
158159
},
160+
...timesOptionsConfig,
159161
})
160162
.showHidden('show-hidden')
161163
.middleware(verboseMiddleware)
@@ -272,6 +274,8 @@ function mainAgentMiddleware(argv) {
272274
agentMockOpenPage,
273275
} = argv;
274276

277+
const timesOption = getTimesOption(argv);
278+
275279
argv.agent = new Agent({
276280
log,
277281
protocol,
@@ -284,6 +288,7 @@ function mainAgentMiddleware(argv) {
284288
atDriverUrl: agentAtDriverUrl,
285289
mock: agentMock,
286290
mockOpenPage: agentMockOpenPage,
291+
timesOption: timesOption,
287292
}),
288293
});
289294
}

src/shared/times-option.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/// <reference path="./types.js" />
2+
3+
/**
4+
* @module shared
5+
*/
6+
7+
/**
8+
* @type AriaATCIShared.timesOption
9+
*/
10+
const timesDefaults = {
11+
afterNav: 1000,
12+
afterKeys: 5000,
13+
testSetup: 1000,
14+
modeSwitch: 750,
15+
docReady: 2000,
16+
};
17+
18+
/**
19+
* Create a yargs description for the specified timesOption.
20+
* @param {keyof AriaATCIShared.timesOption} optionName Key from timesOption
21+
* @param {string} argName The text used for the argument (without leading --)
22+
* @param {string} describe Description to be used in --show-help
23+
*/
24+
function addOptionConfig(optionName, argName, describe) {
25+
timesOptionsArgNameMap.set(optionName, argName);
26+
timesOptionsConfig[argName] = {
27+
hidden: true,
28+
default: timesDefaults[optionName],
29+
describe,
30+
coerce(arg) {
31+
const isNumber = typeof arg === 'number';
32+
if (!isNumber && !arg.match(/^\d+$/)) {
33+
throw new Error('option value not a number');
34+
}
35+
const time = isNumber ? arg : parseInt(arg, 10);
36+
if (time <= 0) {
37+
throw new Error('time must be positive and non-zero');
38+
}
39+
return time;
40+
},
41+
};
42+
}
43+
44+
/**
45+
* @type Map<keyof AriaATCIShared.timesOption, string>
46+
*/
47+
const timesOptionsArgNameMap = new Map();
48+
49+
/**
50+
* the yargs configuration for the time options
51+
*/
52+
export const timesOptionsConfig = {};
53+
addOptionConfig(
54+
'afterNav',
55+
'time-after-nav',
56+
'Timeout used after navigation to collect and discard speech.'
57+
);
58+
addOptionConfig(
59+
'afterKeys',
60+
'time-after-keys',
61+
'Timeout used to wait for speech to finish after pressing keys.'
62+
);
63+
addOptionConfig(
64+
'testSetup',
65+
'time-test-setup',
66+
'Timeout used after pressing test setup button to collect and discard speech.'
67+
);
68+
addOptionConfig(
69+
'modeSwitch',
70+
'time-mode-switch',
71+
'Timeout used after switching modes to check resulting speech (NVDA).'
72+
);
73+
addOptionConfig('docReady', 'time-doc-ready', 'Timeout used waiting for document ready (Safari).');
74+
75+
/**
76+
* Convert the times dictionary to an array of strings to pass back to args.
77+
* @param {AriaATCIShared.timesOption} opts
78+
* @returns {string[]}
79+
*/
80+
export function timesArgs(opts) {
81+
const args = [];
82+
for (const key of Object.keys(opts)) {
83+
const value = opts[key];
84+
// no need to pass on "default" value
85+
if (value == timesDefaults[key]) continue;
86+
// casting in jsdoc syntax is complicated - the extra () around key are
87+
// required to make the type annotation work.
88+
const argName = timesOptionsArgNameMap.get(/** @type keyof AriaATCIShared.timesOption */ (key));
89+
args.push('--' + argName);
90+
args.push(String(value));
91+
}
92+
return args;
93+
}
94+
95+
/**
96+
* Convert the arguments parse result into a timesOption object.
97+
* @param {any} args The parsed arguments
98+
* @returns {AriaATCIShared.timesOption}
99+
*/
100+
export function getTimesOption(args) {
101+
const result = { ...timesDefaults };
102+
for (const key in result) {
103+
const mapped = timesOptionsArgNameMap.get(/** @type keyof AriaATCIShared.timesOption */ (key));
104+
if (mapped) {
105+
if (args[mapped]) result[key] = args[mapped];
106+
}
107+
}
108+
return result;
109+
}

src/shared/types.js

+9
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@
4747
* @param {AriaATCIShared.JobBinding<*>} binding
4848
* @returns {Promise<T>}
4949
*/
50+
51+
/**
52+
* @typedef AriaATCIShared.timesOption
53+
* @property {number} afterNav Timeout used after navigation to collect and discard speech.
54+
* @property {number} afterKeys Timeout used to wait for speech to finish after pressing keys.
55+
* @property {number} testSetup Timeout used after pressing test setup button to collect and discard speech.
56+
* @property {number} modeSwitch Timeout used after switching modes to check resulting speech (NVDA).
57+
* @property {number} docReady Timeout used waiting for document ready (Safari).
58+
*/

0 commit comments

Comments
 (0)