Skip to content

Commit

Permalink
Support externalising attachments in HTML formatter (#2413)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjgoss committed Aug 12, 2024
1 parent 551efa2 commit 7ef4d0e
Show file tree
Hide file tree
Showing 21 changed files with 332 additions and 41 deletions.
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ coverage/
lib/
node_modules
tmp/
reports/*.html
reports/*.ndjson
reports/*.txt
reports/*.xml
reports/
yarn-error.log
.vscode
.DS_Store
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber.

## [Unreleased]
### Added
- Support externalising attachments in HTML formatter (see [documentation](./docs/formatters.md#html)) ([#2413](https://github.com/cucumber/cucumber-js/pull/2413))
- Support linking to external content via attachments (see [documentation](./docs/support_files/attachments.md#links)) ([#2413](https://github.com/cucumber/cucumber-js/pull/2413))

## [10.8.0] - 2024-05-26
### Added
Expand Down
16 changes: 16 additions & 0 deletions docs/formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ You can:
- Filter to specific statuses
- Search by keywords or tag expressions

#### Attachments

By default, the HTML report includes all attachments from your test run as embedded data. This is simple and convenient, with the file being completely standalone and portable. But it can make for a _very_ large file if you have a lot of large attachments like screenshots, videos and other media. You can optionally have attachments saved to external files instead, if that works better for you:

```json
{
"formatOptions": {
"html": {
"externalAttachments": true
}
}
}
```

This will cause attachments to be saved in the same directory as the report file, with filenames that look like `attachment-8e7c5d3d-1ef0-4be6-86e0-16362bad9531.png`. If you want to put the report file somewhere - say, a web server - to be viewed, you'll need to bring those files along with it.

### `message`

Outputs all the [Cucumber Messages](https://github.com/cucumber/messages) for the test run as newline-delimited JSON, which can then be consumed by other tools.
Expand Down
22 changes: 22 additions & 0 deletions docs/support_files/attachments.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,25 @@ After(function () {
```

Anything you log will be attached as a string with a MIME type of `text/x.cucumber.log+plain`

## Links

You can attach one or more links from your support code with the `link` function:

```javascript
var {Before, After} = require('@cucumber/cucumber');

Before(function () {
this.link('https://cucumber.io');
});

After(function () {
this.link(
'https://github.com/cucumber/cucumber-js',
'https://github.com/cucumber/cucumber-jvm',
'https://github.com/cucumber/cucumber-ruby'
);
});
```

Links will be attached as a string with a MIME type of `text/uri-list`
1 change: 1 addition & 0 deletions docs/support_files/world.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ By default, the world is an instance of Cucumber's built-in `World` class. Cucum

* `this.attach`: a method for adding [attachments](./attachments.md) to hooks/steps
* `this.log`: a method for [logging](./attachments.md#logging) information from hooks/steps
* `this.link`: a method for [linking](./attachments.md#links) to URLs from hooks/steps
* `this.parameters`: an object of parameters passed in via configuration (see below)

Your custom world will also receive these arguments, but it's up to you to decide what to do with them and they can be safely ignored.
Expand Down
8 changes: 7 additions & 1 deletion exports/root/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ export interface IWorld<ParametersType = any> {
// (undocumented)
readonly attach: ICreateAttachment;
// (undocumented)
readonly link: ICreateLink;
// (undocumented)
readonly log: ICreateLog;
// (undocumented)
readonly parameters: ParametersType;
Expand All @@ -349,6 +351,8 @@ export interface IWorldOptions<ParametersType = any> {
// (undocumented)
attach: ICreateAttachment;
// (undocumented)
link: ICreateLink;
// (undocumented)
log: ICreateLog;
// (undocumented)
parameters: ParametersType;
Expand Down Expand Up @@ -532,10 +536,12 @@ export const When: IDefineStep_2;

// @public (undocumented)
export class World<ParametersType = any> implements IWorld<ParametersType> {
constructor({ attach, log, parameters }: IWorldOptions<ParametersType>);
constructor({ attach, log, link, parameters, }: IWorldOptions<ParametersType>);
// (undocumented)
readonly attach: ICreateAttachment;
// (undocumented)
readonly link: ICreateLink;
// (undocumented)
readonly log: ICreateLog;
// (undocumented)
readonly parameters: ParametersType;
Expand Down
36 changes: 36 additions & 0 deletions features/html_formatter.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Feature: HTML formatter

Rule: Attachments except logs are externalised based on the externalAttachments option

Background:
Given a file named "features/a.feature" with:
"""
Feature: a feature
Scenario: a scenario
Given a step
"""
And a file named "features/steps.js" with:
"""
const {Given, world} = require('@cucumber/cucumber')
Given('a step', () => {
world.log('Logging some info')
world.link('https://cucumber.io')
world.attach(btoa('Base64 text'), 'base64:text/plain')
world.attach('Plain text', 'text/plain')
})
"""

Scenario: Without externalAttachments option
When I run cucumber-js with `--format html:html.out`
Then it passes
And the html formatter output is complete
And the formatter has no externalised attachments

Scenario: With externalAttachments option
When I run cucumber-js with `--format html:html.out --format-options '{"html":{"externalAttachments":true}}'`
Then it passes
And the html formatter output is complete
And the formatter has these external attachments:
| Base64 text |
| Plain text |
26 changes: 25 additions & 1 deletion features/step_definitions/formatter_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'node:path'
import { expect, use } from 'chai'
import chaiExclude from 'chai-exclude'
import fs from 'mz/fs'
import { Then } from '../../'
import { Then, DataTable } from '../../'
import {
ignorableKeys,
normalizeJsonOutput,
Expand Down Expand Up @@ -69,3 +69,27 @@ Then('the html formatter output is complete', async function (this: World) {
expect(actual).to.contain('<html lang="en">')
expect(actual).to.contain('</html>')
})

Then(
'the formatter has no externalised attachments',
async function (this: World) {
const actual = fs
.readdirSync(this.tmpDir)
.filter((filename) => filename.startsWith('attachment-')).length
expect(actual).to.eq(0)
}
)

Then(
'the formatter has these external attachments:',
async function (this: World, table: DataTable) {
const actual = fs
.readdirSync(this.tmpDir)
.filter((filename) => filename.startsWith('attachment-'))
.map((filename) =>
fs.readFileSync(path.join(this.tmpDir, filename), { encoding: 'utf-8' })
)
actual.sort()
expect(actual).to.deep.eq(table.raw().map((row) => row[0]))
}
)
45 changes: 29 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
"@cucumber/gherkin": "28.0.0",
"@cucumber/gherkin-streams": "5.0.1",
"@cucumber/gherkin-utils": "9.0.0",
"@cucumber/html-formatter": "21.3.1",
"@cucumber/html-formatter": "21.6.0",
"@cucumber/message-streams": "4.0.1",
"@cucumber/messages": "24.1.0",
"@cucumber/tag-expressions": "6.1.0",
Expand All @@ -239,6 +239,7 @@
"lodash.merge": "^4.6.2",
"lodash.mergewith": "^4.6.2",
"luxon": "3.2.1",
"mime": "^3.0.0",
"mkdirp": "^2.1.5",
"mz": "^2.7.0",
"progress": "^2.0.3",
Expand Down
15 changes: 10 additions & 5 deletions src/api/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function initializeFormatters({

async function initializeFormatter(
stream: IFormatterStream,
directory: string | undefined,
target: string,
specifier: string
): Promise<void> {
Expand Down Expand Up @@ -73,21 +74,25 @@ export async function initializeFormatters({
await pluginManager.initFormatter(
implementation,
configuration.options,
stream.write.bind(stream)
logger,
stream.write.bind(stream),
directory
)
if (stream !== stdout) {
cleanupFns.push(promisify<any>(stream.end.bind(stream)))
}
}
}

await initializeFormatter(stdout, 'stdout', configuration.stdout)
await initializeFormatter(stdout, undefined, 'stdout', configuration.stdout)
for (const [target, specifier] of Object.entries(configuration.files)) {
await initializeFormatter(
await createStream(target, onStreamError, cwd, logger),
const { stream, directory } = await createStream(
target,
specifier
onStreamError,
cwd,
logger
)
await initializeFormatter(stream, directory, target, specifier)
}

return async function () {
Expand Down
Loading

0 comments on commit 7ef4d0e

Please sign in to comment.