Skip to content
Permalink
Browse files
feat(protractor): Serenity/JS Cucumber adapter supports native Cucumb…
  • Loading branch information
jan-molak committed Feb 16, 2021
1 parent b15ca47 commit bbf00c0
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 13 deletions.
@@ -184,7 +184,7 @@ To install and configure Serenity/JS reporting modules appropriate for your proj

You can use [native Cucumber reporters](https://github.com/cucumber/cucumber-js/tree/master/src/formatter) together with, or instead of those provided by Serenity/JS.

To do that, specify them using a `<formatterName:outputFileName>` format, for example:
To do that, specify them using a `<formatterName:outputFileName>` format to save their output to a file, or simply `<formatterName>` to print their output to terminal. For example:

```javascript
// protractor.conf.js
@@ -196,10 +196,10 @@ exports.config = {
specs: [ 'features/*.feature', ],
cucumberOpts: {
format: [
'usage', // or 'usage:usage.txt' to print to file
'html:cucumber.html',
'snippets:snippets.txt',
'summary:summary.txt',
'usage:usage.txt',
],
require: [
'features/step_definitions/**/*.ts', // or *.js
@@ -213,6 +213,14 @@ exports.config = {
};
```

<div class="pro-tip">
<div class="icon"><i class="fas fa-lightbulb"></i></div>
<div class="text"><p><strong>PRO TIP:</strong>
Cucumber.js supports <strong>only one native formatter per output</strong>.
In particular, you can't have more than one Cucumber.js formatter writing to standard output or to the same file.
</p></div>
</div>

## Integrating Protractor with Serenity/JS and Jasmine

To integrate Serenity/JS with Protractor and Jasmine, `@serenity-js/protractor/adapter` uses a test runner adapter and a Jasmine reporter available as part of the [`@serenity-js/jasmine` module](/handbook/integration/serenityjs-and-jasmine.html).
@@ -0,0 +1,40 @@
import 'mocha';

import { expect, ifExitCodeIsOtherThan, logOutput } from '@integration/testing-tools';
import { given } from 'mocha-testdata';
import { CucumberRunner, cucumberVersions } from '../src';

describe('@serenity-js/cucumber', function () {

this.timeout(5000);

given([
...cucumberVersions(1, 2)
.thatRequires(
'node_modules/@serenity-js/cucumber/lib/index.js',
'lib/support/configure_serenity.js',
)
.withStepDefsIn('synchronous', 'promise', 'callback')
.withArgs(
'--no-colors',
'--format', 'summary',
)
.toRun('features/passing_scenario.feature'),

...cucumberVersions(3, 4, 5, 6)
.thatRequires('lib/support/configure_serenity.js')
.withStepDefsIn('synchronous', 'promise', 'callback')
.withArgs(
'--format', 'node_modules/@serenity-js/cucumber',
'--format', 'summary',
)
.toRun('features/passing_scenario.feature'),
]).
it('supports native cucumber formatters', (runner: CucumberRunner) => runner.run().
then(ifExitCodeIsOtherThan(0, logOutput)).
then(res => {
expect(res.exitCode).to.equal(0);

expect(res.stdout).to.match(/1 scenario.*?1 passed.*?\n1 step.*?1 passed/);
}));
});
@@ -0,0 +1,46 @@
import 'mocha';

import { given } from 'mocha-testdata';
import { OperatingSystem } from '../../src/io';
import { expect } from '../expect';

/** @test {OperatingSystem} */
describe('OperatingSystem', () => {

const
windows = { os: { platform: () => 'win32' }, proc: { } },
msys = { os: { platform: () => 'linux' }, proc: { env: { OSTYPE: 'msys' } } },
cygwin = { os: { platform: () => 'linux' }, proc: { env: { OSTYPE: 'cygwin' } } },
linux = { os: { platform: () => 'linux' }, proc: { env: { } } },
macos = { os: { platform: () => 'darwin' }, proc: { env: { } } };

const
nullDevice = '\\\\.\\NUL',
devNull = '/dev/null';

given([
{ isWindows: true, ...windows },
{ isWindows: true, ...msys },
{ isWindows: true, ...cygwin },
{ isWindows: false, ...linux },
{ isWindows: false, ...macos },
]).
it('recognises the type of the operating system', ({ os, proc, isWindows }: { os: any, proc: any, isWindows: boolean }) => {
const operatingSystem = new OperatingSystem(os, proc);

expect(operatingSystem.isWindows()).to.equal(isWindows);
});

given([
{ expectedPath: nullDevice, ...windows },
{ expectedPath: nullDevice, ...msys },
{ expectedPath: nullDevice, ...cygwin },
{ expectedPath: devNull, ...linux },
{ expectedPath: devNull, ...macos },
]).
it('tells the null device path', ({ expectedPath, os, proc }: { expectedPath: string, os: any, proc: any }) => {
const operatingSystem = new OperatingSystem(os, proc);

expect(operatingSystem.nullDevicePath()).to.equal(expectedPath);
})
});
@@ -0,0 +1,20 @@
import * as os from 'os';

export class OperatingSystem {
constructor(
private readonly operatingSystem: typeof os,
private readonly proc: typeof process,
) {
}

public nullDevicePath(): string {
return this.isWindows()
? `\\\\.\\NUL`
: `/dev/null`;
}

public isWindows(): boolean {
return this.operatingSystem.platform() === 'win32'
|| /^(msys|cygwin)$/.test(this.proc.env.OSTYPE);
}
}
@@ -9,6 +9,7 @@ export * from './FileSystem';
export * from './FileSystemLocation';
export * from './formatted';
export * from './ModuleLoader';
export * from './OperatingSystem';
export * from './Path';
export * from './trimmed';
export * from './Version';
@@ -1,5 +1,5 @@
/* istanbul ignore file covered in integration tests */
import { ModuleLoader, Version } from '@serenity-js/core/lib/io';
import { ModuleLoader, OperatingSystem, Version } from '@serenity-js/core/lib/io';
import { CucumberConfig } from './CucumberConfig';
import { CucumberOptions } from './CucumberOptions';

@@ -13,6 +13,7 @@ export class CucumberCLIAdapter {
constructor(
config: CucumberConfig,
private readonly loader: ModuleLoader,
private readonly os: OperatingSystem,
) {
this.options = new CucumberOptions(config);
}
@@ -24,7 +25,7 @@ export class CucumberCLIAdapter {

const
argv = this.options.asArgumentsForCucumber(version),
serenityListener = this.loader.resolve('@serenity-js/cucumber');
serenityListener = `${ this.loader.resolve('@serenity-js/cucumber') }:${ this.os.nullDevicePath() }`;

if (version.isAtLeast(new Version('7.0.0'))) {
return this.runWithCucumber7(argv, serenityListener, pathsToScenarios);
@@ -1,18 +1,22 @@
import 'mocha';

import { expect } from '@integration/testing-tools';
import { ModuleLoader, Path } from '@serenity-js/core/lib/io';
import { OperatingSystem, Path } from '@serenity-js/core/lib/io';
import { TestRunnerDetector } from '../../src/adapter';
import { CucumberTestRunner } from '../../src/adapter/runners/CucumberTestRunner';
import { JasmineTestRunner } from '../../src/adapter/runners/JasmineTestRunner';
import { MochaTestRunner } from '../../src/adapter/runners/MochaTestRunner';

describe('TestRunnerDetector', () => {

const
macos = { platform: () => 'darwin' } as any,
proc = { env: {} } as any;

let detector: TestRunnerDetector;

beforeEach(() => {
detector = new TestRunnerDetector(Path.from(__dirname));
detector = new TestRunnerDetector(Path.from(__dirname), new OperatingSystem(macos, proc));
});

describe('when configured with a specific runner', () => {
@@ -2,7 +2,6 @@ import { ArtifactArchiver, Cast, Serenity } from '@serenity-js/core';
import deepmerge = require('deepmerge');
import { isPlainObject } from 'is-plain-object'; // tslint:disable-line:no-var-requires fails when using default import
import { protractor, Runner } from 'protractor';
import { BrowseTheWeb } from '../screenplay';

import { BrowserDetector, StandardisedCapabilities } from './browser-detector';
import { Config } from './Config';
@@ -1,4 +1,4 @@
import { FileFinder, ModuleLoader, Path } from '@serenity-js/core/lib/io';
import { FileFinder, ModuleLoader, OperatingSystem, Path } from '@serenity-js/core/lib/io';
import { isPlainObject } from 'is-plain-object';
import * as path from 'path'
import { Config } from 'protractor';
@@ -22,7 +22,7 @@ export class TestRunnerDetector {
];
}

constructor(cwd: Path) {
constructor(cwd: Path, private readonly os: OperatingSystem) {
this.loader = new ModuleLoader(cwd.value);
this.finder = new FileFinder(cwd);
}
@@ -73,6 +73,7 @@ export class TestRunnerDetector {
return new CucumberTestRunner(
correctedConfig,
this.loader,
this.os,
);
}

@@ -1,8 +1,9 @@
/* istanbul ignore file */

import { serenity } from '@serenity-js/core';
import { FileFinder, ModuleLoader, Path } from '@serenity-js/core/lib/io';
import { OperatingSystem, Path } from '@serenity-js/core/lib/io';
import { Runner } from 'protractor';
import * as os from 'os';
import { ProtractorFrameworkAdapter } from './ProtractorFrameworkAdapter';
import { ProtractorReport } from './reporter';
import { TestRunnerDetector } from './TestRunnerDetector';
@@ -18,6 +19,6 @@ export function run(runner: Runner, specs: string[]): Promise<ProtractorReport>
return new ProtractorFrameworkAdapter(
serenity,
runner,
new TestRunnerDetector(Path.from(runner.getConfig().configDir)),
new TestRunnerDetector(Path.from(runner.getConfig().configDir), new OperatingSystem(os, process)),
).run(specs);
}
@@ -1,4 +1,4 @@
import { ModuleLoader } from '@serenity-js/core/lib/io';
import { ModuleLoader, OperatingSystem } from '@serenity-js/core/lib/io';
import { CucumberCLIAdapter, CucumberConfig } from '@serenity-js/cucumber/lib/cli'; // tslint:disable-line:no-submodule-imports
import { TestRunner } from './TestRunner';

@@ -9,10 +9,11 @@ export class CucumberTestRunner implements TestRunner {
constructor(
private readonly config: CucumberConfig,
private readonly loader: ModuleLoader,
private readonly os: OperatingSystem,
) {
}

run(pathsToScenarios: string[]): Promise<void> {
return new CucumberCLIAdapter(this.config, this.loader).run(pathsToScenarios);
return new CucumberCLIAdapter(this.config, this.loader, this.os).run(pathsToScenarios);
}
}

0 comments on commit bbf00c0

Please sign in to comment.