From c08248be73ea297fd04e553c0ff5aded9a9283cb Mon Sep 17 00:00:00 2001 From: Jeff Witt <152964771+witt3rd@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:25:22 -0500 Subject: [PATCH] Apply TIMEOUT_MULTIPLIER globally across codebase This change centralizes the TIMEOUT_MULTIPLIER environment variable and applies it consistently to all appropriate timeouts throughout Testaro. Changes: - Add procs/config.js as a shared module exporting timeoutMultiplier and an applyMultiplier() helper function - Update run.js to import from shared config and apply multiplier to 15 navigation/interaction timeouts (5000-15000ms range) - Update tests/alfa.js to use multiplier on networkidle wait (also increases base timeout from 2000ms to 6000ms for slow-loading sites) - Update tests/aslint.js to use multiplier on script evaluation timeout - Update tests/testaro.js to import from shared config instead of defining timeoutMultiplier locally - Update procs/screenShot.js to use multiplier on screenshot timeout - Update testaro/tabNav.js to use multiplier on click/keypress timeouts - Update testaro/hover.js to use multiplier on hover timeout Timeouts intentionally NOT modified (fail-fast operations): - tests/alfa.js boundingBox/innerText (50ms) - quick info gathering - procs/identify.js element ID (100ms) - quick element identification Usage: Set TIMEOUT_MULTIPLIER=2 (or higher) for slow networks/sites. Default value is 1 (no change to existing behavior). Co-Authored-By: Claude Opus 4.5 --- procs/config.js | 27 +++++++++++++++++++++++++++ procs/screenShot.js | 7 ++++++- run.js | 34 +++++++++++++++++----------------- testaro/hover.js | 4 +++- testaro/tabNav.js | 9 +++++++-- tests/alfa.js | 3 ++- tests/aslint.js | 4 +++- tests/testaro.js | 3 ++- 8 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 procs/config.js diff --git a/procs/config.js b/procs/config.js new file mode 100644 index 00000000..f76b10ff --- /dev/null +++ b/procs/config.js @@ -0,0 +1,27 @@ +/* + © 2025 Jonathan Robert Pool. + + Licensed under the MIT License. See LICENSE file at the project root or + https://opensource.org/license/mit/ for details. + + SPDX-License-Identifier: MIT +*/ + +/* + config + Shared configuration values for Testaro. +*/ + +// Timeout multiplier from environment variable. +// Set TIMEOUT_MULTIPLIER > 1 for slow networks/sites, < 1 for fast environments. +const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1; + +// Helper to apply multiplier to a timeout value. +// Use for navigation, interaction, and long-running operation timeouts. +// Do NOT use for very short "fail-fast" timeouts (< 100ms). +const applyMultiplier = (baseTimeout) => Math.round(baseTimeout * timeoutMultiplier); + +module.exports = { + timeoutMultiplier, + applyMultiplier +}; diff --git a/procs/screenShot.js b/procs/screenShot.js index 1d68b182..48b91fc1 100644 --- a/procs/screenShot.js +++ b/procs/screenShot.js @@ -11,6 +11,11 @@ firefox browser type. */ +// IMPORTS + +// Shared configuration for timeout multiplier. +const {applyMultiplier} = require('./config'); + // FUNCTIONS // Creates and returns a screenshot. @@ -18,7 +23,7 @@ exports.screenShot = async (page, exclusion = null) => { const options = { fullPage: true, omitBackground: true, - timeout: 4000 + timeout: applyMultiplier(4000) }; if (exclusion) { options.mask = [exclusion]; diff --git a/run.js b/run.js index 8690a0a7..96d6d135 100644 --- a/run.js +++ b/run.js @@ -36,6 +36,8 @@ const {standardize} = require('./procs/standardize'); const {identify} = require('./procs/identify'); // Module to send a notice to an observer. const {tellServer} = require('./procs/tellServer'); +// Shared configuration for timeout multiplier. +const {applyMultiplier, timeoutMultiplier} = require('./procs/config'); // Module to create child processes. const {fork} = require('child_process'); // Module to set operating-system constants. @@ -85,8 +87,6 @@ const timeLimits = { ibm: 30, testaro: 150 + Math.round(6 * waits / 1000) }; -// Timeout multiplier. -const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1; // ########## VARIABLES @@ -397,7 +397,7 @@ const launch = exports.launch = async ( // Reassign the page variable to a new page (tab) of the context (window). page = await browserContext.newPage(); // Wait until it is stable. - await page.waitForLoadState('domcontentloaded', {timeout: 5000}); + await page.waitForLoadState('domcontentloaded', {timeout: applyMultiplier(5000)}); // Add a script to the page to mask automation detection. await page.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); @@ -1113,7 +1113,7 @@ const doActs = async (report, opts = {}) => { if (what === 'url') { // Wait for the URL to be the exact text. try { - await page.waitForURL(which, {timeout: 15000}); + await page.waitForURL(which, {timeout: applyMultiplier(15000)}); result.found = true; result.url = page.url(); } @@ -1135,7 +1135,7 @@ const doActs = async (report, opts = {}) => { which, { polling: 1000, - timeout: 5000 + timeout: applyMultiplier(5000) } ); result.found = true; @@ -1159,7 +1159,7 @@ const doActs = async (report, opts = {}) => { which, { polling: 2000, - timeout: 15000 + timeout: applyMultiplier(15000) } ); result.found = true; @@ -1177,7 +1177,7 @@ const doActs = async (report, opts = {}) => { // Wait for it. const stateIndex = ['loaded', 'idle'].indexOf(act.which); await page.waitForLoadState( - ['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 15000][stateIndex]} + ['domcontentloaded', 'networkidle'][stateIndex], {timeout: applyMultiplier([10000, 15000][stateIndex])} ) // If the wait times out: .catch(async error => { @@ -1199,7 +1199,7 @@ const doActs = async (report, opts = {}) => { // Wait for a page to be created and identify it as current. page = await browserContext.waitForEvent('page'); // Wait until it is idle. - await page.waitForLoadState('networkidle', {timeout: 15000}); + await page.waitForLoadState('networkidle', {timeout: applyMultiplier(15000)}); // Add the resulting URL to the act. const result = { url: page.url() @@ -1319,8 +1319,8 @@ const doActs = async (report, opts = {}) => { const move = isClick ? 'click' : 'Enter keypress'; try { await isClick - ? selection.click({timeout: 4000}) - : selection.press('Enter', {timeout: 4000}); + ? selection.click({timeout: applyMultiplier(4000)}) + : selection.press('Enter', {timeout: applyMultiplier(4000)}); act.result.success = true; act.result.move = move; } @@ -1331,7 +1331,7 @@ const doActs = async (report, opts = {}) => { } if (act.result.success) { try { - await page.context().waitForEvent('networkidle', {timeout: 10000}); + await page.context().waitForEvent('networkidle', {timeout: applyMultiplier(10000)}); act.result.idleTimely = true; } catch(error) { @@ -1345,13 +1345,13 @@ const doActs = async (report, opts = {}) => { // FUNCTION DEFINITION END // If the move is a button click, perform it. if (type === 'button') { - await selection.click({timeout: 3000}); + await selection.click({timeout: applyMultiplier(3000)}); act.result.success = true; act.result.move = 'clicked'; } // Otherwise, if it is checking a radio button or checkbox, perform it. else if (['checkbox', 'radio'].includes(type)) { - await selection.waitForElementState('stable', {timeout: 2000}) + await selection.waitForElementState('stable', {timeout: applyMultiplier(2000)}) .catch(error => { console.log(`ERROR waiting for stable ${type} (${error.message})`); act.result.success = false; @@ -1362,7 +1362,7 @@ const doActs = async (report, opts = {}) => { if (isEnabled) { await selection.check({ force: true, - timeout: 2000 + timeout: applyMultiplier(2000) }) .catch(error => { console.log(`ERROR checking ${type} (${error.message})`); @@ -1383,7 +1383,7 @@ const doActs = async (report, opts = {}) => { } // Otherwise, if it is focusing the element, perform it. else if (type === 'focus') { - await selection.focus({timeout: 2000}); + await selection.focus({timeout: applyMultiplier(2000)}); act.result.success = true; act.result.move = 'focused'; } @@ -1402,9 +1402,9 @@ const doActs = async (report, opts = {}) => { else { // Click the link and wait for the resulting navigation. try { - await selection.click({timeout: 5000}); + await selection.click({timeout: applyMultiplier(5000)}); // Wait for the new content to load. - await page.waitForLoadState('domcontentloaded', {timeout: 6000}); + await page.waitForLoadState('domcontentloaded', {timeout: applyMultiplier(6000)}); act.result.success = true; act.result.move = 'clicked'; act.result.newURL = page.url(); diff --git a/testaro/hover.js b/testaro/hover.js index 9f0d7f46..1e16316d 100644 --- a/testaro/hover.js +++ b/testaro/hover.js @@ -19,6 +19,8 @@ const {getBasicResult, getVisibleCountChange} = require('../procs/testaro'); // Module to perform Playwright operations. const playwright = require('playwright'); +// Shared configuration for timeout multiplier. +const {applyMultiplier} = require('../procs/config'); // FUNCTIONS @@ -71,7 +73,7 @@ exports.reporter = async (page, withItems) => { const elementCount0 = await loc0.count(); try { // Hover over the element. - await loc.hover({timeout: 400}); + await loc.hover({timeout: applyMultiplier(400)}); // Get the change in the count of the visible elements in the observation tree. const changeData = await getVisibleCountChange(rootLoc, elementCount0, 400, 75); const {change, elapsedTime} = changeData; diff --git a/testaro/tabNav.js b/testaro/tabNav.js index 3be358d3..427111b4 100644 --- a/testaro/tabNav.js +++ b/testaro/tabNav.js @@ -13,6 +13,11 @@ This test reports nonstandard keyboard navigation among tab elements in visible tab lists. Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel. */ +// IMPORTS + +// Shared configuration for timeout multiplier. +const {applyMultiplier} = require('../procs/config'); + // CONSTANTS const data = {}; @@ -111,7 +116,7 @@ const testKey = async ( let pressed = true; // Click the tab element, to make the focus on it effective. await tabElement.click({ - timeout: 500 + timeout: applyMultiplier(500) }) .catch(async error => { console.log( @@ -135,7 +140,7 @@ const testKey = async ( if (pressed) { // Refocus the tab element and press the specified key (page.keyboard.press may fail). await tabElement.press(keyName, { - timeout: 1000 + timeout: applyMultiplier(1000) }) .catch(error => { console.log(`ERROR: could not press ${keyName} (${error.message})`); diff --git a/tests/alfa.js b/tests/alfa.js index fda1ac3b..f172d042 100644 --- a/tests/alfa.js +++ b/tests/alfa.js @@ -21,6 +21,7 @@ const {cap, tidy} = require('../procs/job'); const {getIdentifiers} = require('../procs/standardize'); const {getNormalizedXPath} = require('../procs/identify'); const {Playwright} = require('@siteimprove/alfa-playwright'); +const {applyMultiplier} = require('../procs/config'); // FUNCTIONS @@ -57,7 +58,7 @@ exports.reporter = async (page, report, actIndex) => { } try { // Wait for a stable page to make the page and its alfa version consistent. - await page.waitForLoadState('networkidle', {timeout: 2000}); + await page.waitForLoadState('networkidle', {timeout: applyMultiplier(6000)}); const doc = await page.evaluateHandle('document'); const alfaPage = await Playwright.toPage(doc); // Test the page content with the specified rules. diff --git a/tests/aslint.js b/tests/aslint.js index 742c0432..dc5f1536 100644 --- a/tests/aslint.js +++ b/tests/aslint.js @@ -25,6 +25,8 @@ const fs = require('fs/promises'); const {getElementData} = require('../procs/getElementData'); // Function to normalize an XPath. const {getNormalizedXPath} = require('../procs/identify'); +// Shared configuration for timeout multiplier. +const {applyMultiplier} = require('../procs/config'); // CONSTANTS @@ -194,7 +196,7 @@ exports.reporter = async (page, report, actIndex) => { // Wait for the test results to be attached to the page. const waitOptions = { state: 'attached', - timeout: 20000 + timeout: applyMultiplier(20000) }; await reportLoc.waitFor(waitOptions); } diff --git a/tests/testaro.js b/tests/testaro.js index 0b62df4c..a86d0a76 100644 --- a/tests/testaro.js +++ b/tests/testaro.js @@ -17,6 +17,8 @@ // Function to launch a browser. const {launch} = require('../run'); +// Shared configuration for timeout multiplier. +const {timeoutMultiplier} = require('../procs/config'); // CONSTANTS @@ -401,7 +403,6 @@ const allRules = [ defaultOn: false } ]; -const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1; // ERROR HANDLER process.on('unhandledRejection', reason => {