From 108677e213423004127b6752301e73f66231030e Mon Sep 17 00:00:00 2001 From: Jan Molak <1089173+jan-molak@users.noreply.github.com> Date: Mon, 31 Jul 2023 22:37:33 +0100 Subject: [PATCH] feat(core): allow for easier debugging of Serenity/JS domain events using StreamReporter Related tickets: re #1790 --- .../serenity-js.org/docs/design/debugging.mdx | 8 +++ .../stream-reporter/StreamReporter.spec.ts | 20 +++++- .../crew/stream-reporter/StreamReporter.ts | 67 ++++++++++++++----- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/documentation/serenity-js.org/docs/design/debugging.mdx b/documentation/serenity-js.org/docs/design/debugging.mdx index 8cea63f5c33..1617444eabd 100644 --- a/documentation/serenity-js.org/docs/design/debugging.mdx +++ b/documentation/serenity-js.org/docs/design/debugging.mdx @@ -120,3 +120,11 @@ await actorCalled('Debbie').attemptsTo( }, PlaywrightPage.current().nativePage()), ); ``` + +## Inspecting Serenity/JS domain events + +Serenity/JS uses [domain events](/handbook/reporting/domain-events/) to facilitate communication between [actors](/api/core/class/Actor) +and [stage crew members](/api/core/interface/StageCrewMember/) (a.k.a. reporting services). + +If you're developing a [custom `StageCrewMember`](/api/core/interface/StageCrewMember/), or [suspect a bug in Serenity/JS](/contributing/#report-an-issue) itself, +you can inspect those events using the [`StreamReporter`](/api/core/class/StreamReporter/). diff --git a/packages/core/spec/stage/crew/stream-reporter/StreamReporter.spec.ts b/packages/core/spec/stage/crew/stream-reporter/StreamReporter.spec.ts index 89e48c27ab4..f5279accd58 100644 --- a/packages/core/spec/stage/crew/stream-reporter/StreamReporter.spec.ts +++ b/packages/core/spec/stage/crew/stream-reporter/StreamReporter.spec.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, it } from 'mocha'; import * as sinon from 'sinon'; import { Writable } from 'stream'; -import type { Actor, Cast} from '../../../../src'; +import type { Actor, Cast } from '../../../../src'; import { Clock, Duration, ErrorFactory, Stage, StageManager, StreamReporter, Timestamp } from '../../../../src'; import { TestRunFinished } from '../../../../src/events'; import { ExecutionSuccessful } from '../../../../src/model'; @@ -44,4 +44,22 @@ describe('StreamReporter', () => { `{"type":"TestRunFinished","event":{"outcome":{"code":64},"timestamp":"2021-01-13T18:00:00.000Z"}}\n` ); }); + + describe('when configured using JSON', () => { + + it('can be instantiated when the outputFile is provided', async () => { + const outputFile = './events.ndjson.log'; + const cwd = __dirname; + + const reporter = StreamReporter.fromJSON({ outputFile, cwd }).assignedTo(stage); + + expect(reporter).to.be.instanceOf(StreamReporter); + }); + + it('complains when the outputFile is not provided', () => { + expect(() => { + StreamReporter.fromJSON({ outputFile: undefined }); + }).to.throw(Error, 'outputFile should be defined'); + }); + }); }); diff --git a/packages/core/src/stage/crew/stream-reporter/StreamReporter.ts b/packages/core/src/stage/crew/stream-reporter/StreamReporter.ts index a6a124f1fcc..62813c3a69e 100644 --- a/packages/core/src/stage/crew/stream-reporter/StreamReporter.ts +++ b/packages/core/src/stage/crew/stream-reporter/StreamReporter.ts @@ -1,12 +1,14 @@ import type { Writable } from 'stream'; +import { ensure, isDefined, isString } from 'tiny-types'; import type { DomainEvent } from '../../../events'; +import { FileSystem, Path } from '../../../io'; import type { Stage } from '../../Stage'; import type { StageCrewMember } from '../../StageCrewMember'; /** * Serialises all the {@apilink DomainEvent} objects it receives and streams - * them as [ndjson](http://ndjson.org/) to the output stream. + * them as [ndjson](http://ndjson.org/) to the output stream or file. * * Useful when debugging issues related to custom Serenity/JS test runner adapters. * @@ -35,43 +37,61 @@ import type { StageCrewMember } from '../../StageCrewMember'; * }) * ``` * - * ## Registering `StreamReporter` using Protractor configuration + * ## Using `StreamReporter` with Playwright Test + * + * ```ts + * // playwright.config.ts + * import type { PlaywrightTestConfig } from '@serenity-js/playwright-test' + * + * const config: PlaywrightTestConfig = { + * testDir: './spec', + * + * reporter: [ + * [ '@serenity-js/playwright-test', { + * crew: [ + * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }] + * ] + * // other Serenity/JS config + * }] + * ], + * // other Playwright Test config + * } + * ``` + * + * ## Using `StreamReporter` with Protractor * * ```js * // protractor.conf.js - * const { StreamReporter } = require('@serenity-js/core'); - * * exports.config = { * framework: 'custom', * frameworkPath: require.resolve('@serenity-js/protractor/adapter'), * * serenity: { * crew: [ - * new StreamReporter(process.stdout), + * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }] * ], * // other Serenity/JS config * }, * // other Protractor config - * }; + * } * ``` * - * ## Registering `StreamReporter` using WebdriverIO configuration + * ## Using `StreamReporter` with WebdriverIO * * ```ts - * // wdio.conf.js - * import { StreamReporter } from '@serenity-js/core' + * // wdio.conf.ts * import { WebdriverIOConfig } from '@serenity-js/webdriverio' * * export const config: WebdriverIOConfig = { * - * framework: '@serenity-js/webdriverio', + * framework: '@serenity-js/webdriverio', * - * serenity: { - * crew: [ - * new StreamReporter(process.stdout), - * ] - * // other Serenity/JS config - * }, + * serenity: { + * crew: [ + * '@serenity-js/serenity-bdd', + * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }] + * ] + * // other Serenity/JS config * }, * // other WebdriverIO config * } @@ -81,6 +101,21 @@ import type { StageCrewMember } from '../../StageCrewMember'; */ export class StreamReporter implements StageCrewMember { + /** + * Instantiates a `StreamReporter` outputting a stream of {@apilink DomainEvent|domain events} + * to an `outputFile` at the given location. + * + * @param config + */ + static fromJSON(config: { outputFile: string, cwd?: string }): StageCrewMember { + const outputFile = ensure('outputFile', config?.outputFile, isDefined(), isString()); + const cwd = config.cwd || process.cwd(); + + const fs = new FileSystem(Path.from(cwd)) + + return new StreamReporter(fs.createWriteStreamTo(Path.from(outputFile))); + } + /** * @param {stream~Writable} output * A Writable stream that should receive the output