Skip to content

Commit

Permalink
feat(core): allow for easier debugging of Serenity/JS domain events u…
Browse files Browse the repository at this point in the history
…sing StreamReporter

Related tickets: re #1790
  • Loading branch information
jan-molak committed Jul 31, 2023
1 parent bbffabe commit 108677e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 17 deletions.
8 changes: 8 additions & 0 deletions documentation/serenity-js.org/docs/design/debugging.mdx
Expand Up @@ -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/).
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
});
});
});
67 changes: 51 additions & 16 deletions 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.
*
Expand Down Expand Up @@ -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
* }
Expand All @@ -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
Expand Down

0 comments on commit 108677e

Please sign in to comment.