Skip to content

Commit 30f93e6

Browse files
committed
Add integration with @cucumber/html-formatter
I believe this will benefit many. Fixes #780.
1 parent 81ea97b commit 30f93e6

File tree

9 files changed

+231
-32
lines changed

9 files changed

+231
-32
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
66

77
- Updated all `@cucumber/*` dependencies.
88

9+
- Added native support for HTML reports using `@cucumber/html-formatter`, fixes [#780](https://github.com/badeball/cypress-cucumber-preprocessor/issues/780).
10+
911
## v11.5.1
1012

1113
- Expose member `getStepDefinitionPatterns`.

docs/configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ Every configuration option has a similar key which can be use to override it, sh
5555

5656
| JSON path | Environment key | Example(s) |
5757
|--------------------|-------------------|------------------------------------------|
58-
| `stepDefinitions` | `stepDefinitions` | `[filepath].{js,ts}` |
58+
| `stepDefinitions` | `stepDefinitions` | `[filepath].{js,ts}` |
5959
| `messages.enabled` | `messagesEnabled` | `true`, `false` |
6060
| `messages.output` | `messagesOutput` | `cucumber-messages.ndjson` |
6161
| `json.enabled` | `jsonEnabled` | `true`, `false` |
6262
| `json.formatter` | `jsonFormatter` | `/usr/bin/cucumber-json-formatter` |
6363
| `json.args ` | `jsonArgs ` | `["custom-json-formatter.js"]` |
6464
| `json.output` | `jsonOutput` | `cucumber-report.json` |
65+
| `html.enabled` | `htmlEnabled` | `true`, `false` |
66+
| `html.output` | `htmlOutput` | `cucumber-report.html` |
6567
| `filterSpecs` | `filterSpecs` | `true`, `false` |
6668
| `omitFiltered` | `omitFiltered` | `true`, `false` |
6769

docs/html-report.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# HTML reports
2+
3+
HTML reports can be enabled using the `html.enabled` property. The preprocessor uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), which means you can place configuration options in EG. `.cypress-cucumber-preprocessorrc.json` or `package.json`. An example configuration is shown below.
4+
5+
```json
6+
{
7+
"html": {
8+
"enabled": true
9+
}
10+
}
11+
```
12+
13+
This **requires** you to have registered this module in your [configuration file](https://docs.cypress.io/guides/references/configuration#Configuration-File), as shown below.
14+
15+
```ts
16+
import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
17+
18+
export default defineConfig({
19+
e2e: {
20+
async setupNodeEvents(
21+
on: Cypress.PluginEvents,
22+
config: Cypress.PluginConfigOptions
23+
) {
24+
await addCucumberPreprocessorPlugin(on, config);
25+
26+
// Make sure to return the config object as it might have been modified by the plugin.
27+
return config;
28+
},
29+
},
30+
});
31+
```

docs/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* [Step definitions](step-definitions.md)
88
* [Tags](tags.md)
99
* [JSON report](json-report.md)
10+
* [HTML report](html-report.md)
1011
* [Localisation](localisation.md)
1112
* [Configuration](configuration.md)
1213
* [Test configuration](test-configuration.md)

features/html_report.feature

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Feature: html report
2+
Background:
3+
Given additional preprocessor configuration
4+
"""
5+
{
6+
"html": {
7+
"enabled": true
8+
}
9+
}
10+
"""
11+
12+
Scenario: basic report
13+
Given a file named "cypress/e2e/a.feature" with:
14+
"""
15+
Feature: a feature
16+
Scenario: a scenario
17+
Given a step
18+
"""
19+
And a file named "cypress/support/step_definitions/steps.js" with:
20+
"""
21+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
22+
Given("a step", function() {})
23+
"""
24+
When I run cypress
25+
Then it passes
26+
And there should be a HTML report
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Then } from "@cucumber/cucumber";
2+
import path from "path";
3+
import { promises as fs } from "fs";
4+
import assert from "assert";
5+
6+
Then("there should be a HTML report", async function () {
7+
await assert.doesNotReject(
8+
() => fs.access(path.join(this.tmpDir, "cucumber-report.html")),
9+
"Expected there to be a HTML file"
10+
);
11+
});

lib/add-cucumber-preprocessor-plugin.ts

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ import path from "path";
44

55
import child_process from "child_process";
66

7+
import stream from "stream";
8+
79
import chalk from "chalk";
810

11+
import resolvePkg from "resolve-pkg";
12+
13+
import { NdjsonToMessageStream } from "@cucumber/message-streams";
14+
15+
import CucumberHtmlStream from "@cucumber/html-formatter";
16+
917
import messages, { IdGenerator, SourceMediaType } from "@cucumber/messages";
1018

1119
import parse from "@cucumber/tag-expressions";
@@ -80,7 +88,7 @@ export async function beforeRunHandler(config: Cypress.PluginConfigOptions) {
8088
export async function afterRunHandler(config: Cypress.PluginConfigOptions) {
8189
const preprocessor = await resolve(config, config.env);
8290

83-
if (!preprocessor.json.enabled) {
91+
if (!preprocessor.json.enabled && !preprocessor.html.enabled) {
8492
return;
8593
}
8694

@@ -89,50 +97,88 @@ export async function afterRunHandler(config: Cypress.PluginConfigOptions) {
8997
preprocessor.messages.output
9098
);
9199

92-
const jsonPath = ensureIsAbsolute(
93-
config.projectRoot,
94-
preprocessor.json.output
95-
);
96-
97100
try {
98101
await fs.access(messagesPath, fsConstants.F_OK);
99102
} catch {
100103
return;
101104
}
102105

103-
await fs.mkdir(path.dirname(jsonPath), { recursive: true });
106+
if (preprocessor.json.enabled) {
107+
const jsonPath = ensureIsAbsolute(
108+
config.projectRoot,
109+
preprocessor.json.output
110+
);
104111

105-
const messages = await fs.open(messagesPath, "r");
112+
await fs.mkdir(path.dirname(jsonPath), { recursive: true });
106113

107-
try {
108-
const json = await fs.open(jsonPath, "w");
114+
const messages = await fs.open(messagesPath, "r");
109115

110116
try {
111-
const { formatter, args } = preprocessor.json;
112-
const child = child_process.spawn(formatter, args, {
113-
stdio: [messages.fd, json.fd, "inherit"],
114-
});
115-
116-
await new Promise<void>((resolve, reject) => {
117-
child.on("exit", (code) => {
118-
if (code === 0) {
119-
resolve();
120-
} else {
121-
reject(
122-
new Error(
123-
`${preprocessor.json.formatter} exited non-successfully`
124-
)
125-
);
126-
}
117+
const json = await fs.open(jsonPath, "w");
118+
119+
try {
120+
const { formatter, args } = preprocessor.json;
121+
const child = child_process.spawn(formatter, args, {
122+
stdio: [messages.fd, json.fd, "inherit"],
127123
});
128124

129-
child.on("error", reject);
130-
});
125+
await new Promise<void>((resolve, reject) => {
126+
child.on("exit", (code) => {
127+
if (code === 0) {
128+
resolve();
129+
} else {
130+
reject(
131+
new Error(
132+
`${preprocessor.json.formatter} exited non-successfully`
133+
)
134+
);
135+
}
136+
});
137+
138+
child.on("error", reject);
139+
});
140+
} finally {
141+
await json.close();
142+
}
131143
} finally {
132-
await json.close();
144+
await messages.close();
133145
}
134-
} finally {
135-
await messages.close();
146+
}
147+
148+
if (preprocessor.html.enabled) {
149+
const htmlPath = ensureIsAbsolute(
150+
config.projectRoot,
151+
preprocessor.html.output
152+
);
153+
154+
await fs.mkdir(path.dirname(htmlPath), { recursive: true });
155+
156+
const input = syncFs.createReadStream(messagesPath);
157+
158+
const output = syncFs.createWriteStream(htmlPath);
159+
160+
console.log("I am invoked!");
161+
162+
await new Promise<void>((resolve, reject) => {
163+
stream.pipeline(
164+
input,
165+
new NdjsonToMessageStream(),
166+
new CucumberHtmlStream(
167+
resolvePkg("@cucumber/html-formatter", { cwd: __dirname }) +
168+
"/dist/main.css",
169+
resolvePkg("@cucumber/html-formatter", { cwd: __dirname }) +
170+
"/dist/main.js"
171+
),
172+
output,
173+
(err) => {
174+
if (err) {
175+
reject(err);
176+
} else {
177+
resolve();
178+
}
179+
}
180+
);
181+
});
136182
}
137183
}
138184

lib/preprocessor-configuration.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,37 @@ function validateConfigurationEntry(
119119
};
120120
return { [key]: messagesConfig };
121121
}
122+
case "html": {
123+
if (typeof value !== "object" || value == null) {
124+
throw new Error(
125+
`Expected an object (json), but got ${util.inspect(value)}`
126+
);
127+
}
128+
let args: string[] | undefined;
129+
if (
130+
!hasOwnProperty(value, "enabled") ||
131+
typeof value.enabled !== "boolean"
132+
) {
133+
throw new Error(
134+
`Expected a boolean (html.enabled), but got ${util.inspect(value)}`
135+
);
136+
}
137+
let output: string | undefined;
138+
if (hasOwnProperty(value, "output")) {
139+
if (isString(value.output)) {
140+
output = value.output;
141+
} else {
142+
throw new Error(
143+
`Expected a string (html.output), but got ${util.inspect(value)}`
144+
);
145+
}
146+
}
147+
const messagesConfig = {
148+
enabled: value.enabled,
149+
output,
150+
};
151+
return { [key]: messagesConfig };
152+
}
122153
case "filterSpecs": {
123154
if (!isBoolean(value)) {
124155
throw new Error(
@@ -243,6 +274,32 @@ function validateEnvironmentOverrides(
243274
}
244275
}
245276

277+
if (hasOwnProperty(environment, "htmlEnabled")) {
278+
const { htmlEnabled } = environment;
279+
280+
if (isBoolean(htmlEnabled)) {
281+
overrides.htmlEnabled = htmlEnabled;
282+
} else if (isString(htmlEnabled)) {
283+
overrides.htmlEnabled = stringToMaybeBoolean(htmlEnabled);
284+
} else {
285+
throw new Error(
286+
`Expected a boolean (htmlEnabled), but got ${util.inspect(htmlEnabled)}`
287+
);
288+
}
289+
}
290+
291+
if (hasOwnProperty(environment, "htmlOutput")) {
292+
const { htmlOutput } = environment;
293+
294+
if (isString(htmlOutput)) {
295+
overrides.htmlOutput = htmlOutput;
296+
} else {
297+
throw new Error(
298+
`Expected a string (htmlOutput), but got ${util.inspect(htmlOutput)}`
299+
);
300+
}
301+
}
302+
246303
if (hasOwnProperty(environment, "filterSpecs")) {
247304
const { filterSpecs } = environment;
248305

@@ -302,6 +359,10 @@ export interface IPreprocessorConfiguration {
302359
formatter?: string;
303360
output?: string;
304361
};
362+
readonly html?: {
363+
enabled: boolean;
364+
output?: string;
365+
};
305366
readonly filterSpecs?: boolean;
306367
readonly omitFiltered?: boolean;
307368
}
@@ -314,6 +375,8 @@ export interface IEnvironmentOverrides {
314375
jsonEnabled?: boolean;
315376
jsonFormatter?: string;
316377
jsonOutput?: string;
378+
htmlEnabled?: boolean;
379+
htmlOutput?: string;
317380
filterSpecs?: boolean;
318381
omitFiltered?: boolean;
319382
}
@@ -364,6 +427,7 @@ export class PreprocessorConfiguration implements IPreprocessorConfiguration {
364427
return {
365428
enabled:
366429
this.json.enabled ||
430+
this.html.enabled ||
367431
(this.environmentOverrides.messagesEnabled ??
368432
this.explicitValues.messages?.enabled ??
369433
false),
@@ -394,6 +458,19 @@ export class PreprocessorConfiguration implements IPreprocessorConfiguration {
394458
};
395459
}
396460

461+
get html() {
462+
return {
463+
enabled:
464+
this.environmentOverrides.htmlEnabled ??
465+
this.explicitValues.html?.enabled ??
466+
false,
467+
output:
468+
this.environmentOverrides.htmlOutput ??
469+
this.explicitValues.html?.output ??
470+
"cucumber-report.html",
471+
};
472+
}
473+
397474
get filterSpecs() {
398475
return (
399476
this.environmentOverrides.filterSpecs ??

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"@badeball/cypress-configuration": "^4.0.0",
4646
"@cucumber/cucumber-expressions": "^16.0.0",
4747
"@cucumber/gherkin": "^24.0.0",
48+
"@cucumber/html-formatter": "^19.2.0",
49+
"@cucumber/message-streams": "^4.0.1",
4850
"@cucumber/messages": "^19.1.2",
4951
"@cucumber/tag-expressions": "^4.1.0",
5052
"base64-js": "^1.5.1",
@@ -53,6 +55,7 @@
5355
"debug": "^4.2.0",
5456
"glob": "^7.2.0",
5557
"is-path-inside": "^3.0.3",
58+
"resolve-pkg": "^2.0.0",
5659
"uuid": "^8.3.2"
5760
},
5861
"devDependencies": {

0 commit comments

Comments
 (0)