@web/test-runner-core
Advanced tools
Comparing version 0.6.17 to 0.6.18
# @web/test-runner-core | ||
## 0.6.18 | ||
### Patch Changes | ||
- 736d101: improve scheduling logic and error handling | ||
## 0.6.17 | ||
@@ -4,0 +10,0 @@ |
@@ -34,2 +34,8 @@ import { CoverageMapData } from 'istanbul-lib-coverage'; | ||
/** | ||
* Returns whether this session is currently active. If it is active, stopSession | ||
* can be called. | ||
* @param session | ||
*/ | ||
isActive(session: TestSession): boolean; | ||
/** | ||
* Stops a single test session. There is no mandatory action to be taken here. | ||
@@ -36,0 +42,0 @@ * Implementations can use this for example to recycle inactive tabs instead of |
@@ -39,6 +39,6 @@ import { BrowserLauncher } from '../browser-launcher/BrowserLauncher.js'; | ||
browserStartTimeout?: number; | ||
sessionStartTimeout?: number; | ||
sessionFinishTimeout?: number; | ||
testsStartTimeout?: number; | ||
testsFinishTimeout?: number; | ||
staticLogging?: boolean; | ||
} | ||
//# sourceMappingURL=TestRunnerCoreConfig.d.ts.map |
@@ -17,3 +17,3 @@ "use strict"; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
@@ -23,3 +23,3 @@ return result; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.constants = void 0; | ||
exports.EventEmitter = exports.SESSION_STATUS = exports.TestSessionManager = exports.TestRunner = exports.constants = void 0; | ||
const constants = __importStar(require("./browser-launcher/constants")); | ||
@@ -26,0 +26,0 @@ exports.constants = constants; |
@@ -126,3 +126,2 @@ "use strict"; | ||
try { | ||
this.scheduler.runScheduled(this.testRun); | ||
const finishedAll = Array.from(this.sessions.all()).every(s => s.status === TestSessionStatus_1.SESSION_STATUS.FINISHED); | ||
@@ -129,0 +128,0 @@ if (finishedAll) { |
@@ -9,13 +9,24 @@ import { TestRunnerCoreConfig } from '../config/TestRunnerCoreConfig'; | ||
constructor(config: TestRunnerCoreConfig, sessions: TestSessionManager); | ||
/** | ||
* Schedules a session for execution. Execution is batched, the session | ||
* will be queued until there is a browser page available. | ||
*/ | ||
schedule(testRun: number, sessionsToSchedule: Iterable<TestSession>): void; | ||
stop(): void; | ||
private addTimeoutId; | ||
private clearTimeouts; | ||
schedule(testRun: number, sessionsToSchedule: Iterable<TestSession>): void; | ||
runScheduled(testRun: number): void; | ||
/** Runs the next batch of scheduled sessions, if any. */ | ||
private runNextScheduled; | ||
private startSession; | ||
private setSessionFailed; | ||
private setSessionStartedTimeout; | ||
private setSessionFinishedTimeout; | ||
stopSession(session: TestSession, errors?: TestResultError[]): Promise<void>; | ||
private waitForTestsStarted; | ||
private waitForTestsFinished; | ||
/** | ||
* Returns whether the session has gone stale. Sessions are immutable, this takes in a | ||
* snapshot of a session and returns whether the session has since changed test run or status. | ||
* This can be used to decide whether perform side effects like logging or changing status. | ||
*/ | ||
private isStale; | ||
private addTimeoutId; | ||
private clearTimeouts; | ||
} | ||
//# sourceMappingURL=TestScheduler.d.ts.map |
@@ -11,13 +11,32 @@ "use strict"; | ||
this.timeoutIdsPerSession = new Map(); | ||
sessions.on('session-status-updated', session => { | ||
sessions.on('session-status-updated', async (session) => { | ||
if (session.status === TestSessionStatus_1.SESSION_STATUS.TEST_STARTED) { | ||
this.waitForTestsFinished(session.testRun, session.id); | ||
return; | ||
} | ||
if (session.status === TestSessionStatus_1.SESSION_STATUS.TEST_FINISHED) { | ||
this.stopSession(session); | ||
// the session finished executing tests, close the browser page | ||
await this.stopSession(session); | ||
return; | ||
} | ||
const timeoutIds = this.timeoutIdsPerSession.get(session.id); | ||
if (timeoutIds && session.status === TestSessionStatus_1.SESSION_STATUS.FINISHED) { | ||
this.clearTimeouts(timeoutIds); | ||
if (session.status === TestSessionStatus_1.SESSION_STATUS.FINISHED) { | ||
// the session is full finished, clear any related timeouts | ||
const timeoutIds = this.timeoutIdsPerSession.get(session.id); | ||
if (timeoutIds) { | ||
this.clearTimeouts(timeoutIds); | ||
} | ||
this.runNextScheduled(); | ||
} | ||
}); | ||
} | ||
/** | ||
* Schedules a session for execution. Execution is batched, the session | ||
* will be queued until there is a browser page available. | ||
*/ | ||
schedule(testRun, sessionsToSchedule) { | ||
for (const session of sessionsToSchedule) { | ||
this.sessions.updateStatus(Object.assign(Object.assign({}, session), { testRun, request404s: [] }), TestSessionStatus_1.SESSION_STATUS.SCHEDULED); | ||
} | ||
this.runNextScheduled(); | ||
} | ||
stop() { | ||
@@ -28,40 +47,22 @@ for (const ids of this.timeoutIdsPerSession.values()) { | ||
} | ||
addTimeoutId(sessionId, id) { | ||
let timeoutIds = this.timeoutIdsPerSession.get(sessionId); | ||
if (!timeoutIds) { | ||
timeoutIds = []; | ||
this.timeoutIdsPerSession.set(sessionId, timeoutIds); | ||
} | ||
timeoutIds.push(id); | ||
} | ||
clearTimeouts(timeoutIds) { | ||
for (const id of timeoutIds) { | ||
clearTimeout(id); | ||
} | ||
} | ||
schedule(testRun, sessionsToSchedule) { | ||
for (const session of sessionsToSchedule) { | ||
this.sessions.updateStatus(Object.assign(Object.assign({}, session), { request404s: [] }), TestSessionStatus_1.SESSION_STATUS.SCHEDULED); | ||
} | ||
this.runScheduled(testRun); | ||
} | ||
runScheduled(testRun) { | ||
const scheduledIt = this.sessions.forStatus(TestSessionStatus_1.SESSION_STATUS.SCHEDULED); | ||
/** Runs the next batch of scheduled sessions, if any. */ | ||
runNextScheduled() { | ||
const runningCount = Array.from(this.sessions.forStatus(TestSessionStatus_1.SESSION_STATUS.INITIALIZING, TestSessionStatus_1.SESSION_STATUS.TEST_STARTED, TestSessionStatus_1.SESSION_STATUS.TEST_FINISHED)).length; | ||
const count = this.config.concurrency - runningCount; | ||
for (let i = 0; i < count; i += 1) { | ||
const { done, value } = scheduledIt.next(); | ||
if (done || !value) { | ||
break; | ||
} | ||
this.startSession(testRun, value); | ||
if (count === 0) { | ||
return; | ||
} | ||
const scheduled = Array.from(this.sessions.forStatus(TestSessionStatus_1.SESSION_STATUS.SCHEDULED)).slice(0, count); | ||
for (const session of scheduled) { | ||
this.startSession(session); | ||
} | ||
} | ||
async startSession(testRun, session) { | ||
this.sessions.update(Object.assign(Object.assign({}, session), { testRun, status: TestSessionStatus_1.SESSION_STATUS.INITIALIZING })); | ||
let browserStartResponded = false; | ||
async startSession(session) { | ||
const updatedSession = Object.assign(Object.assign({}, session), { status: TestSessionStatus_1.SESSION_STATUS.INITIALIZING }); | ||
this.sessions.update(updatedSession); | ||
let browserStarted = false; | ||
// browser should be started within the specified milliseconds | ||
const timeoutId = setTimeout(() => { | ||
if (!browserStartResponded) { | ||
this.setSessionFailed(this.sessions.get(session.id), { | ||
if (!browserStarted && !this.isStale(updatedSession)) { | ||
this.setSessionFailed(this.sessions.get(updatedSession.id), { | ||
message: `The browser was unable to open the test page after ${this.config.browserStartTimeout}ms.`, | ||
@@ -71,13 +72,20 @@ }); | ||
}, this.config.browserStartTimeout); | ||
this.addTimeoutId(session.id, timeoutId); | ||
this.addTimeoutId(updatedSession.id, timeoutId); | ||
try { | ||
await session.browserLauncher.startSession(session, createSessionUrl_1.createSessionUrl(this.config, session, false)); | ||
await updatedSession.browserLauncher.startSession(updatedSession, createSessionUrl_1.createSessionUrl(this.config, updatedSession, false)); | ||
// when the browser started, wait for session to ping back on time | ||
this.setSessionStartedTimeout(testRun, session.id); | ||
this.waitForTestsStarted(updatedSession.testRun, updatedSession.id); | ||
} | ||
catch (error) { | ||
this.setSessionFailed(session, error); | ||
if (this.isStale(updatedSession)) { | ||
// something else has changed the test session, such as a the browser timeout | ||
// or a re-run in watch mode. in that was we just log the error | ||
this.config.logger.error(error); | ||
} | ||
else { | ||
this.setSessionFailed(updatedSession, { message: error.message, stack: error.stack }); | ||
} | ||
} | ||
finally { | ||
browserStartResponded = true; | ||
browserStarted = true; | ||
} | ||
@@ -88,3 +96,29 @@ } | ||
} | ||
setSessionStartedTimeout(testRun, sessionId) { | ||
async stopSession(session, errors = []) { | ||
var _a; | ||
if (this.isStale(session)) { | ||
return; | ||
} | ||
const sessionErrors = [...errors]; | ||
const updatedSession = Object.assign({}, session); | ||
try { | ||
if (session.browserLauncher.isActive(session)) { | ||
const { testCoverage, browserLogs } = await session.browserLauncher.stopSession(session); | ||
updatedSession.testCoverage = testCoverage; | ||
updatedSession.logs = browserLogs; | ||
} | ||
} | ||
catch (error) { | ||
sessionErrors.push(error); | ||
} | ||
finally { | ||
if (sessionErrors.length > 0) { | ||
// merge with existing erors | ||
updatedSession.errors = [...((_a = updatedSession.errors) !== null && _a !== void 0 ? _a : []), ...sessionErrors]; | ||
updatedSession.passed = false; | ||
} | ||
this.sessions.updateStatus(updatedSession, TestSessionStatus_1.SESSION_STATUS.FINISHED); | ||
} | ||
} | ||
waitForTestsStarted(testRun, sessionId) { | ||
const timeoutId = setTimeout(() => { | ||
@@ -98,15 +132,11 @@ const session = this.sessions.get(sessionId); | ||
this.setSessionFailed(session, { | ||
message: `Browser tests did not start after ${this.config.sessionStartTimeout}ms. Check the browser logs or open the browser in debug mode for more information.`, | ||
message: `Browser tests did not start after ${this.config.testsStartTimeout}ms. ` + | ||
'Check the browser logs or open the browser in debug mode for more information.', | ||
}); | ||
return; | ||
} | ||
if ([TestSessionStatus_1.SESSION_STATUS.TEST_FINISHED, TestSessionStatus_1.SESSION_STATUS.FINISHED].includes(session.status)) { | ||
// The session finished by now | ||
return; | ||
} | ||
this.setSessionFinishedTimeout(testRun, session.id); | ||
}, this.config.sessionStartTimeout); | ||
}, this.config.testsStartTimeout); | ||
this.addTimeoutId(sessionId, timeoutId); | ||
} | ||
setSessionFinishedTimeout(testRun, sessionId) { | ||
waitForTestsFinished(testRun, sessionId) { | ||
const timeoutId = setTimeout(() => { | ||
@@ -118,23 +148,37 @@ const session = this.sessions.get(sessionId); | ||
} | ||
if (session.status !== TestSessionStatus_1.SESSION_STATUS.FINISHED) { | ||
if (session.status !== TestSessionStatus_1.SESSION_STATUS.TEST_FINISHED) { | ||
this.setSessionFailed(session, { | ||
message: `Browser tests did not finish within ${this.config.sessionStartTimeout}ms. Check the browser logs or open the browser in debug mode for more information.`, | ||
message: `Browser tests did not finish within ${this.config.testsFinishTimeout}ms. ` + | ||
'Check the browser logs or open the browser in debug mode for more information.', | ||
}); | ||
} | ||
}, this.config.sessionFinishTimeout); | ||
}, this.config.testsFinishTimeout); | ||
this.addTimeoutId(sessionId, timeoutId); | ||
} | ||
async stopSession(session, errors = []) { | ||
var _a; | ||
const { testCoverage, browserLogs: logs } = await session.browserLauncher.stopSession(session); | ||
const updatedSession = Object.assign(Object.assign({}, session), { testCoverage, logs }); | ||
if (errors.length > 0) { | ||
// merge with existing erors | ||
updatedSession.errors = [...((_a = updatedSession.errors) !== null && _a !== void 0 ? _a : []), ...errors]; | ||
updatedSession.passed = false; | ||
/** | ||
* Returns whether the session has gone stale. Sessions are immutable, this takes in a | ||
* snapshot of a session and returns whether the session has since changed test run or status. | ||
* This can be used to decide whether perform side effects like logging or changing status. | ||
*/ | ||
isStale(session) { | ||
const currentSession = this.sessions.get(session.id); | ||
return (!currentSession || | ||
currentSession.testRun !== session.testRun || | ||
currentSession.status !== session.status); | ||
} | ||
addTimeoutId(sessionId, id) { | ||
let timeoutIds = this.timeoutIdsPerSession.get(sessionId); | ||
if (!timeoutIds) { | ||
timeoutIds = []; | ||
this.timeoutIdsPerSession.set(sessionId, timeoutIds); | ||
} | ||
this.sessions.updateStatus(updatedSession, TestSessionStatus_1.SESSION_STATUS.FINISHED); | ||
timeoutIds.push(id); | ||
} | ||
clearTimeouts(timeoutIds) { | ||
for (const id of timeoutIds) { | ||
clearTimeout(id); | ||
} | ||
} | ||
} | ||
exports.TestScheduler = TestScheduler; | ||
//# sourceMappingURL=TestScheduler.js.map |
{ | ||
"name": "@web/test-runner-core", | ||
"version": "0.6.17", | ||
"version": "0.6.18", | ||
"publishConfig": { | ||
@@ -43,4 +43,6 @@ "access": "public" | ||
"@types/istanbul-lib-coverage": "^2.0.2", | ||
"@types/uuid": "^8.0.0" | ||
"@types/uuid": "^8.0.0", | ||
"sinon": "^9.0.2", | ||
"sinon-chai": "^3.5.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81230
990
4