@serenity-js/playwright-test
Version:
Serenity/JS test runner adapter for Playwright Test, combining Playwright's developer experience with the advanced reporting and automation capabilities of Serenity/JS
147 lines • 7.83 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlaywrightEventBuffer = void 0;
const node_path_1 = __importDefault(require("node:path"));
const core_1 = require("@serenity-js/core");
const events_1 = require("@serenity-js/core/lib/events");
const io_1 = require("@serenity-js/core/lib/io");
const model_1 = require("@serenity-js/core/lib/model");
const WorkerEventStreamReader_1 = require("../api/WorkerEventStreamReader");
const WorkerEventStreamWriter_1 = require("../api/WorkerEventStreamWriter");
const events_2 = require("../events");
const PlaywrightErrorParser_1 = require("./PlaywrightErrorParser");
class PlaywrightEventBuffer {
errorParser = new PlaywrightErrorParser_1.PlaywrightErrorParser();
eventStreamReader = new WorkerEventStreamReader_1.WorkerEventStreamReader();
eventFactory;
events = new Map();
deferredSceneFinishedEvents = new Map();
configure(config) {
this.eventFactory = new events_2.EventFactory(io_1.Path.from(config.rootDir));
}
appendTestStart(test, result) {
this.events.set(this.sceneId(test, result).value, this.eventFactory.createSceneStartEvents(test, result));
}
appendRetryableSceneEvents(test, result) {
const sceneId = this.sceneId(test, result);
const sceneEndTime = new core_1.Timestamp(result.startTime).plus(core_1.Duration.ofMilliseconds(result.duration));
this.events.get(sceneId.value).push(new events_1.RetryableSceneDetected(sceneId, sceneEndTime));
if (result.retry > 0 || result.status !== 'passed') {
this.events.get(sceneId.value).push(new events_1.SceneTagged(sceneId, new model_1.ArbitraryTag('retried'), // todo: replace with a dedicated tag
sceneEndTime));
}
}
deferAppendingSceneFinishedEvent(test, result) {
const sceneId = this.sceneId(test, result);
const scenarioOutcome = this.outcomeFrom(test, result);
this.deferredSceneFinishedEvents.set(sceneId.value, {
event: this.eventFactory.createSceneFinishedEvent(test, result, scenarioOutcome),
outputDirectory: test.parent.project().outputDir,
workerIndex: result.workerIndex,
});
}
determineScenarioOutcome(worstInteractionOutcome, scenarioOutcome) {
if (worstInteractionOutcome instanceof model_1.ExecutionFailedWithAssertionError) {
return worstInteractionOutcome;
}
return worstInteractionOutcome.isWorseThan(scenarioOutcome)
? worstInteractionOutcome
: scenarioOutcome;
}
appendCrashedWorkerEvents(test, result) {
const workerStreamId = WorkerEventStreamWriter_1.WorkerEventStreamWriter.workerStreamIdFor(result.workerIndex).value;
const sceneId = this.sceneId(test, result);
this.events.get(sceneId.value).push(...this.readEventStream(test.parent.project().outputDir, workerStreamId, sceneId.value));
}
appendSceneEvents(test, result) {
const sceneId = this.sceneId(test, result);
this.events.get(sceneId.value).push(...this.readEventStream(test.parent.project().outputDir, sceneId.value));
}
readEventStream(outputDirectory, streamId, expectedSceneId = streamId) {
const pathToEventStreamFile = node_path_1.default.join(outputDirectory, 'serenity', streamId, 'events.ndjson');
if (this.eventStreamReader.hasStream(pathToEventStreamFile)) {
return this.eventStreamReader.read(pathToEventStreamFile, (event) => {
// re-attach events from orphaned beforeAll to the test case
const hasSceneId = event.value['sceneId'] !== undefined;
const isAttachedToScene = event.value['sceneId'] === expectedSceneId;
if (hasSceneId && !isAttachedToScene) {
event.value['sceneId'] = expectedSceneId;
}
return event;
});
}
return [];
}
appendSceneFinishedEvent(test, result) {
const sceneId = this.sceneId(test, result);
const worstInteractionOutcome = this.determineWorstInteractionOutcome(this.events.get(sceneId.value));
const scenarioOutcome = this.determineScenarioOutcome(worstInteractionOutcome, this.outcomeFrom(test, result));
this.events.get(sceneId.value).push(this.eventFactory.createSceneFinishedEvent(test, result, scenarioOutcome));
}
flush(test, result) {
const sceneId = this.sceneId(test, result);
const events = this.events.get(sceneId.value);
if (!events) {
throw new core_1.LogicError(`No events found for test: ${sceneId.value}`);
}
this.events.delete(sceneId.value);
return events;
}
flushAllDeferred() {
const allEvents = [];
for (const [testId, events] of this.events.entries()) {
const scenarioEvents = [];
scenarioEvents.push(...events);
if (this.deferredSceneFinishedEvents.has(testId)) {
const lastRecordedEvent = scenarioEvents.at(-1);
const deferredSceneFinished = this.deferredSceneFinishedEvents.get(testId);
const eventStream = this.readEventStream(deferredSceneFinished.outputDirectory, deferredSceneFinished.event.sceneId.value);
const firstEventSinceLastIndex = eventStream.findIndex(event => lastRecordedEvent.equals(event));
const eventsSinceLast = firstEventSinceLastIndex === -1
? eventStream
: eventStream.slice(firstEventSinceLastIndex);
scenarioEvents.push(...eventsSinceLast);
const worstInteractionOutcome = this.determineWorstInteractionOutcome(scenarioEvents);
const sceneFinishedEvent = new events_1.SceneFinished(deferredSceneFinished.event.sceneId, deferredSceneFinished.event.details, this.determineScenarioOutcome(worstInteractionOutcome, deferredSceneFinished.event.outcome), deferredSceneFinished.event.timestamp);
scenarioEvents.push(sceneFinishedEvent);
}
allEvents.push(...scenarioEvents);
}
this.events.clear();
this.deferredSceneFinishedEvents.clear();
return allEvents;
}
determineWorstInteractionOutcome(events) {
let worstInteractionOutcome = new model_1.ExecutionSuccessful();
for (const event of events) {
if (event instanceof events_1.InteractionFinished && event.outcome.isWorseThan(worstInteractionOutcome)) {
worstInteractionOutcome = event.outcome;
}
}
return worstInteractionOutcome;
}
outcomeFrom(test, result) {
const outcome = test.outcome();
if (outcome === 'skipped') {
return new model_1.ExecutionSkipped();
}
if (outcome === 'unexpected' && result.status === 'passed') {
return new model_1.ExecutionFailedWithError(new core_1.LogicError(`Scenario expected to fail, but ${result.status}`));
}
if (['failed', 'interrupted', 'timedOut'].includes(result.status)) {
if (test.retries > result.retry) {
return new model_1.ExecutionIgnored(this.errorParser.errorFrom(result.error));
}
return new model_1.ExecutionFailedWithError(this.errorParser.errorFrom(result.error));
}
return new model_1.ExecutionSuccessful();
}
sceneId(test, result) {
return events_2.PlaywrightSceneId.from(test.parent.project()?.name, test, result);
}
}
exports.PlaywrightEventBuffer = PlaywrightEventBuffer;
//# sourceMappingURL=PlaywrightEventBuffer.js.map