diff --git a/listeners.js b/listeners.js index ef4a6bd..9603106 100644 --- a/listeners.js +++ b/listeners.js @@ -86,6 +86,14 @@ // Send acknowledgement to the server. send( "ack" ); + QUnit.on( "error", function( error ) { + send( "error", { + message: error.message, + name: error.name, + stack: error.stack + } ); + } ); + QUnit.on( "testEnd", function( data ) { send( "testEnd", data ); } ); diff --git a/reporter.js b/reporter.js index ceef537..cb01799 100644 --- a/reporter.js +++ b/reporter.js @@ -113,6 +113,17 @@ export function reportTest( test, { fullBrowser, id } ) { } } +export function reportError( error ) { + const title = `${ error.name || "Error" }: ${ error.message }`; + let message = chalk.red( title ); + + // Chromium error stacks include the title in the first line, + // but Firefox error stacks do not. + message += `\n${ chalk.gray( error.stack.replace( `${ title }\n`, "" ) ) }`; + console.error( `\n\n${ message }` ); + return message; +} + export function reportEnd( result, { descriptiveUrl, fullBrowser, id } ) { console.log( `\n\nTests finished in ${ prettyMs( result.runtime ) } ` + diff --git a/run.js b/run.js index e754fdf..0fc6f35 100644 --- a/run.js +++ b/run.js @@ -3,7 +3,7 @@ import { asyncExitHook, gracefulExit } from "exit-hook"; import { getLatestBrowser } from "./browserstack/api.js"; import { buildBrowserFromString } from "./browserstack/buildBrowserFromString.js"; import { localTunnel } from "./browserstack/local.js"; -import { reportEnd, reportTest } from "./reporter.js"; +import { reportEnd, reportError, reportTest } from "./reporter.js"; import { createTestServer } from "./createTestServer.js"; import { buildTestUrl } from "./lib/buildTestUrl.js"; import { generateHash, generateModuleId } from "./lib/generateHash.js"; @@ -85,10 +85,10 @@ export async function run( { const reportId = message.id; const report = reports[ reportId ]; touchBrowser( report.browser ); - const errors = reportTest( message.data, report ); + const errorMessage = reportTest( message.data, report ); pendingErrors[ reportId ] ??= Object.create( null ); - if ( errors ) { - pendingErrors[ reportId ][ message.data.name ] = errors; + if ( errorMessage ) { + pendingErrors[ reportId ][ message.data.name ] = errorMessage; } else { const existing = pendingErrors[ reportId ][ message.data.name ]; @@ -106,6 +106,15 @@ export async function run( { } break; } + case "error": { + const reportId = message.id; + const report = reports[ reportId ]; + touchBrowser( report.browser ); + const errorMessage = reportError( message.data ); + pendingErrors[ reportId ] ??= Object.create( null ); + pendingErrors[ reportId ][ message.data.message ] = errorMessage; + break; + } case "runEnd": { const reportId = message.id; const report = reports[ reportId ]; @@ -127,6 +136,9 @@ export async function run( { return; } errorMessages.push( ...Object.values( pendingErrors[ reportId ] ) ); + if ( !errorMessages.length ) { + errorMessages.push( `Global failure in ${ report.url }` ); + } } // Run the next test @@ -335,9 +347,9 @@ export async function run( { stop = true; console.error( chalk.red( - `No tests were run with URL "${ report.url }" in ${ getBrowserString( - report.browser - ) } (${ report.id })` + `No tests were run with URL "${ report.url }" in ${ + report.fullBrowser + } (${ report.id })` ) ); } @@ -351,7 +363,8 @@ export async function run( { gracefulExit( 0 ); } } else { - console.error( chalk.red( `${ errorMessages.length } tests failed.` ) ); + const len = errorMessages.length; + console.error( chalk.red( `${ len } test${ len > 1 ? "s" : "" } failed.` ) ); console.log( errorMessages.map( ( error, i ) => `\n${ i + 1 }. ${ error }` ).join( "\n" ) );