diff --git a/__tests__/core/runner.test.ts b/__tests__/core/runner.test.ts index 9d6cbf81..49ce1ffd 100644 --- a/__tests__/core/runner.test.ts +++ b/__tests__/core/runner.test.ts @@ -309,4 +309,54 @@ describe('runner', () => { 'afterAll2', ]); }); + + it('run - supports custom reporters', async () => { + let reporter; + class Reporter { + messages: string[] = []; + + constructor( + public readonly runner: Runner, + public readonly options: any + ) { + reporter = this; + + this.runner.on('start', ({ numJourneys }) => { + this.messages.push(`numJourneys ${numJourneys}`); + }); + this.runner.on('journey:start', ({ journey }) => { + this.messages.push(`journey:start ${journey.name}`); + }); + this.runner.on('journey:end', ({ journey }) => { + this.messages.push(`journey:end ${journey.name}`); + }); + this.runner.on('end', () => { + this.messages.push(`end`); + }); + } + } + + runner.addJourney(new Journey({ name: 'foo' }, noop)); + const result = await runner.run({ + reporter: Reporter, + wsEndpoint, + outfd: fs.openSync(dest, 'w'), + }); + + expect(result).toEqual({ + foo: { + status: 'succeeded', + }, + }); + expect(reporter?.messages).toEqual([ + 'numJourneys 1', + 'journey:start foo', + 'journey:end foo', + 'end', + ]); + expect(reporter.runner).toBeInstanceOf(Runner); + expect(reporter.options).toEqual({ + fd: expect.any(Number), + }); + }); }); diff --git a/src/core/runner.ts b/src/core/runner.ts index 2ff76a7f..bfd338ff 100644 --- a/src/core/runner.ts +++ b/src/core/runner.ts @@ -26,7 +26,7 @@ import { EventEmitter } from 'events'; import { Journey } from '../dsl/journey'; import { Step } from '../dsl/step'; -import { reporters } from '../reporters'; +import { reporters, Reporter } from '../reporters'; import { getMonotonicTime, getTimestamp, runParallel } from '../helpers'; import { StatusValue, @@ -42,9 +42,16 @@ import { log } from './logger'; export type RunOptions = Omit< CliArgs, - 'debug' | 'json' | 'pattern' | 'inline' | 'require' | 'suiteParams' + | 'debug' + | 'json' + | 'pattern' + | 'inline' + | 'require' + | 'suiteParams' + | 'reporter' > & { params?: RunParamaters; + reporter?: CliArgs['reporter'] | Reporter; }; type RunParamaters = Record; @@ -301,7 +308,10 @@ export default class Runner { /** * Set up the corresponding reporter and fallback */ - const Reporter = reporters[reporter] || reporters['default']; + const Reporter = + typeof reporter === 'function' + ? reporter + : reporters[reporter] || reporters['default']; new Reporter(this, { fd: outfd }); this.emit('start', { numJourneys: this.journeys.length }); await this.runBeforeAllHook(); diff --git a/src/index.ts b/src/index.ts index b5037e4b..1f4776a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,3 +65,9 @@ export type { ChromiumBrowserContext, CDPSession, } from 'playwright-chromium'; + +/** + * Export the types necessary to write custom reporters + */ +export type { default as Runner } from './core/runner'; +export type { Reporter, ReporterOptions } from './reporters'; diff --git a/src/reporters/base.ts b/src/reporters/base.ts index 8a064c58..a7281907 100644 --- a/src/reporters/base.ts +++ b/src/reporters/base.ts @@ -33,11 +33,7 @@ import { findPWLogsIndexes, rewriteErrorStack, } from '../helpers'; - -export type ReporterOptions = { - fd?: number; - colors?: boolean; -}; +import { ReporterOptions } from './reporter'; function renderError(error) { let output = ''; diff --git a/src/reporters/index.ts b/src/reporters/index.ts index 0e7d9673..7f5cc0b6 100644 --- a/src/reporters/index.ts +++ b/src/reporters/index.ts @@ -26,7 +26,9 @@ import BaseReporter from './base'; import JSONReporter from './json'; import JUnitReporter from './junit'; +import Reporter, { ReporterOptions } from './reporter'; +export { Reporter, ReporterOptions }; export const reporters = { default: BaseReporter, json: JSONReporter, diff --git a/src/reporters/reporter.ts b/src/reporters/reporter.ts new file mode 100644 index 00000000..dbe5c7b9 --- /dev/null +++ b/src/reporters/reporter.ts @@ -0,0 +1,35 @@ +/** + * MIT License + * + * Copyright (c) 2020-present, Elastic NV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +import Runner from '../core/runner'; + +export type ReporterOptions = { + fd?: number; + colors?: boolean; +}; + +export default interface Reporter { + new (runner: Runner, options: ReporterOptions): any; +}