diff options
author | Timmy Willison <timmywil@users.noreply.github.com> | 2024-04-01 10:23:36 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-01 10:23:36 -0400 |
commit | 284b082eb86602705519d6ca754c40f6d2f8fcc0 (patch) | |
tree | be30f1d0656bdab531f9eeefacc2a99e7d6d70bf /test/runner/browserstack | |
parent | 691c0aeeded5dea1ca2a0c5474c7adfdb1dadffe (diff) | |
download | jquery-284b082eb86602705519d6ca754c40f6d2f8fcc0.tar.gz jquery-284b082eb86602705519d6ca754c40f6d2f8fcc0.zip |
Tests: share queue/browser handling for all worker types
- one queue to rule them all: browserstack, selenium, and jsdom
- retries and hard retries are now supported in selenium
- selenium tests now re-use browsers in the same way as browserstack
Close gh-5460
Diffstat (limited to 'test/runner/browserstack')
-rw-r--r-- | test/runner/browserstack/browsers.js | 211 | ||||
-rw-r--r-- | test/runner/browserstack/queue.js | 119 |
2 files changed, 0 insertions, 330 deletions
diff --git a/test/runner/browserstack/browsers.js b/test/runner/browserstack/browsers.js deleted file mode 100644 index 6489cf965..000000000 --- a/test/runner/browserstack/browsers.js +++ /dev/null @@ -1,211 +0,0 @@ -import chalk from "chalk"; -import { getBrowserString } from "../lib/getBrowserString.js"; -import { createWorker, deleteWorker, getAvailableSessions } from "./api.js"; - -const workers = Object.create( null ); - -/** - * Keys are browser strings - * Structure of a worker: - * { - * debug: boolean, // Stops the worker from being cleaned up when finished - * id: string, - * lastTouch: number, // The last time a request was received - * url: string, - * browser: object, // The browser object - * options: object // The options to create the worker - * } - */ - -// Acknowledge the worker within the time limit. -// BrowserStack can take much longer spinning up -// some browsers, such as iOS 15 Safari. -const ACKNOWLEDGE_INTERVAL = 1000; -const ACKNOWLEDGE_TIMEOUT = 60 * 1000 * 5; - -const MAX_WORKER_RESTARTS = 5; - -// No report after the time limit -// should refresh the worker -const RUN_WORKER_TIMEOUT = 60 * 1000 * 2; - -const WORKER_WAIT_TIME = 30000; - -export function touchBrowser( browser ) { - const fullBrowser = getBrowserString( browser ); - const worker = workers[ fullBrowser ]; - if ( worker ) { - worker.lastTouch = Date.now(); - } -} - -async function waitForAck( worker, { fullBrowser, verbose } ) { - delete worker.lastTouch; - return new Promise( ( resolve, reject ) => { - const interval = setInterval( () => { - if ( worker.lastTouch ) { - if ( verbose ) { - console.log( `\n${ fullBrowser } acknowledged.` ); - } - clearTimeout( timeout ); - clearInterval( interval ); - resolve(); - } - }, ACKNOWLEDGE_INTERVAL ); - - const timeout = setTimeout( () => { - clearInterval( interval ); - reject( - new Error( - `${ fullBrowser } not acknowledged after ${ - ACKNOWLEDGE_TIMEOUT / 1000 / 60 - }min.` - ) - ); - }, ACKNOWLEDGE_TIMEOUT ); - } ); -} - -async function restartWorker( worker ) { - await cleanupWorker( worker, worker.options ); - await createBrowserWorker( - worker.url, - worker.browser, - worker.options, - worker.restarts + 1 - ); -} - -export async function restartBrowser( browser ) { - const fullBrowser = getBrowserString( browser ); - const worker = workers[ fullBrowser ]; - if ( worker ) { - await restartWorker( worker ); - } -} - -async function ensureAcknowledged( worker ) { - const fullBrowser = getBrowserString( worker.browser ); - const verbose = worker.options.verbose; - try { - await waitForAck( worker, { fullBrowser, verbose } ); - return worker; - } catch ( error ) { - console.error( error.message ); - await restartWorker( worker ); - } -} - -export async function createBrowserWorker( url, browser, options, restarts = 0 ) { - if ( restarts > MAX_WORKER_RESTARTS ) { - throw new Error( - `Reached the maximum number of restarts for ${ chalk.yellow( - getBrowserString( browser ) - ) }` - ); - } - const verbose = options.verbose; - while ( ( await getAvailableSessions() ) <= 0 ) { - if ( verbose ) { - console.log( "\nWaiting for available sessions..." ); - } - await new Promise( ( resolve ) => setTimeout( resolve, WORKER_WAIT_TIME ) ); - } - - const { debug, runId, tunnelId } = options; - const fullBrowser = getBrowserString( browser ); - - const worker = await createWorker( { - ...browser, - url: encodeURI( url ), - project: "jquery", - build: `Run ${ runId }`, - - // This is the maximum timeout allowed - // by BrowserStack. We do this because - // we control the timeout in the runner. - // See https://github.com/browserstack/api/blob/b324a6a5bc1b6052510d74e286b8e1c758c308a7/README.md#timeout300 - timeout: 1800, - - // Not documented in the API docs, - // but required to make local testing work. - // See https://www.browserstack.com/docs/automate/selenium/manage-multiple-connections#nodejs - "browserstack.local": true, - "browserstack.localIdentifier": tunnelId - } ); - - browser.debug = !!debug; - worker.url = url; - worker.browser = browser; - worker.restarts = restarts; - worker.options = options; - touchBrowser( browser ); - workers[ fullBrowser ] = worker; - - // Wait for the worker to show up in the list - // before returning it. - return ensureAcknowledged( worker ); -} - -export async function setBrowserWorkerUrl( browser, url ) { - const fullBrowser = getBrowserString( browser ); - const worker = workers[ fullBrowser ]; - if ( worker ) { - worker.url = url; - } -} - -/** - * Checks that all browsers have received - * a response in the given amount of time. - * If not, the worker is restarted. - */ -export async function checkLastTouches() { - for ( const [ fullBrowser, worker ] of Object.entries( workers ) ) { - if ( Date.now() - worker.lastTouch > RUN_WORKER_TIMEOUT ) { - const options = worker.options; - if ( options.verbose ) { - console.log( - `\nNo response from ${ chalk.yellow( fullBrowser ) } in ${ - RUN_WORKER_TIMEOUT / 1000 / 60 - }min.` - ); - } - await restartWorker( worker ); - } - } -} - -export async function cleanupWorker( worker, { verbose } ) { - for ( const [ fullBrowser, w ] of Object.entries( workers ) ) { - if ( w === worker ) { - delete workers[ fullBrowser ]; - await deleteWorker( worker.id ); - if ( verbose ) { - console.log( `\nStopped ${ fullBrowser }.` ); - } - return; - } - } -} - -export async function cleanupAllBrowsers( { verbose } ) { - const workersRemaining = Object.values( workers ); - const numRemaining = workersRemaining.length; - if ( numRemaining ) { - try { - await Promise.all( - workersRemaining.map( ( worker ) => deleteWorker( worker.id ) ) - ); - if ( verbose ) { - console.log( - `Stopped ${ numRemaining } browser${ numRemaining > 1 ? "s" : "" }.` - ); - } - } catch ( error ) { - - // Log the error, but do not consider the test run failed - console.error( error ); - } - } -} diff --git a/test/runner/browserstack/queue.js b/test/runner/browserstack/queue.js deleted file mode 100644 index 6d1c8d51f..000000000 --- a/test/runner/browserstack/queue.js +++ /dev/null @@ -1,119 +0,0 @@ -import chalk from "chalk"; -import { getBrowserString } from "../lib/getBrowserString.js"; -import { - checkLastTouches, - createBrowserWorker, - restartBrowser, - setBrowserWorkerUrl -} from "./browsers.js"; - -const TEST_POLL_TIMEOUT = 1000; - -const queue = []; - -export function getNextBrowserTest( reportId ) { - const index = queue.findIndex( ( test ) => test.id === reportId ); - if ( index === -1 ) { - return; - } - - // Remove the completed test from the queue - const previousTest = queue[ index ]; - queue.splice( index, 1 ); - - // Find the next test for the same browser - for ( const test of queue.slice( index ) ) { - if ( test.fullBrowser === previousTest.fullBrowser ) { - - // Set the URL for our tracking - setBrowserWorkerUrl( test.browser, test.url ); - test.running = true; - - // Return the URL for the next test. - // listeners.js will use this to set the browser URL. - return { url: test.url }; - } - } -} - -export function retryTest( reportId, maxRetries ) { - if ( !maxRetries ) { - return; - } - const test = queue.find( ( test ) => test.id === reportId ); - if ( test ) { - test.retries++; - if ( test.retries <= maxRetries ) { - console.log( - `Retrying test ${ reportId } for ${ chalk.yellow( - test.options.modules.join( ", " ) - ) }...${ test.retries }` - ); - return test; - } - } -} - -export async function hardRetryTest( reportId, maxHardRetries ) { - if ( !maxHardRetries ) { - return false; - } - const test = queue.find( ( test ) => test.id === reportId ); - if ( test ) { - test.hardRetries++; - if ( test.hardRetries <= maxHardRetries ) { - console.log( - `Hard retrying test ${ reportId } for ${ chalk.yellow( - test.options.modules.join( ", " ) - ) }...${ test.hardRetries }` - ); - await restartBrowser( test.browser ); - return true; - } - } - return false; -} - -export function addBrowserStackRun( url, browser, options ) { - queue.push( { - browser, - fullBrowser: getBrowserString( browser ), - hardRetries: 0, - id: options.reportId, - url, - options, - retries: 0, - running: false - } ); -} - -export async function runAllBrowserStack() { - return new Promise( async( resolve, reject ) => { - while ( queue.length ) { - try { - await checkLastTouches(); - } catch ( error ) { - reject( error ); - } - - // Run one test URL per browser at a time - const browsersTaken = []; - for ( const test of queue ) { - if ( browsersTaken.indexOf( test.fullBrowser ) > -1 ) { - continue; - } - browsersTaken.push( test.fullBrowser ); - if ( !test.running ) { - test.running = true; - try { - await createBrowserWorker( test.url, test.browser, test.options ); - } catch ( error ) { - reject( error ); - } - } - } - await new Promise( ( resolve ) => setTimeout( resolve, TEST_POLL_TIMEOUT ) ); - } - resolve(); - } ); -} |