Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support attachments with filenames #2297

Merged
merged 13 commits into from
Jul 12, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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 attachments with filenames ([#2297](https://github.com/cucumber/cucumber-js/pull/2297))

## [9.2.0] - 2023-06-22
### Added
Expand Down
23 changes: 23 additions & 0 deletions compatibility/features/attachments/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,26 @@ When('the {word} png is attached', async function (filename) {
'image/png'
)
})

When(
'a PDF document is attached with a filename',
async function (this: World) {
await this.attach(
fs.createReadStream(
path.join(
process.cwd(),
'node_modules',
'@cucumber',
'compatibility-kit',
'features',
'attachments',
'document.pdf'
)
),
{
mediaType: 'application/pdf',
fileName: 'document.pdf',
}
)
}
)
73 changes: 38 additions & 35 deletions docs/support_files/attachments.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,52 @@ After(function () {
```

By default, text is saved with a MIME type of `text/plain`. You can also specify
a different MIME type:
a different MIME type as part of a second argument:

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

After(function () {
this.attach('{"name": "some JSON"}', 'application/json');
this.attach('{"name": "some JSON"}', { mediaType: 'application/json' });
});
```

If you'd like, you can also specify a filename to be used if the attachment is made available to download as a file via a formatter:

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

After(function () {
this.attach('{"name": "some JSON"}', {
mediaType: 'application/json',
fileName: 'results.json'
});
});
```

Images and other binary data can be attached using a [stream.Readable](https://nodejs.org/api/stream.html).
The data will be `base64` encoded in the output.
You should wait for the stream to be read before continuing by providing a callback or waiting for the returned promise to resolve.
You should wait for the stream to be read before continuing by awaiting the returned promise or providing a callback.

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

// Passing a callback
After(function (testCase, callback) {
// Awaiting the promise
After(async function (testCase) {
if (testCase.result.status === Status.FAILED) {
var stream = getScreenshotOfError();
this.attach(stream, 'image/png', callback);
}
else {
callback();
await this.attach(stream, { mediaType: 'image/png' });
}
});

// Returning the promise
After(function (testCase) {
// Passing a callback
After(function (testCase, callback) {
if (testCase.result.status === Status.FAILED) {
var stream = getScreenshotOfError();
return this.attach(stream, 'image/png');
this.attach(stream, { mediaType: 'image/png' }, callback);
}
else {
callback();
}
});
```
Expand All @@ -60,38 +73,23 @@ var {After, Status} = require('@cucumber/cucumber');
After(function (testCase) {
if (testCase.result.status === Status.FAILED) {
var buffer = getScreenshotOfError();
this.attach(buffer, 'image/png');
}
});
```

If you've already got a base64-encoded string, you can prefix your mime type with `base64:` to indicate this:

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

After(function (testCase) {
if (testCase.result.status === Status.FAILED) {
var base64String = getScreenshotOfError();
this.attach(base64String, 'base64:image/png');
this.attach(buffer, { mediaType: 'image/png' });
}
});
```

Here is an example of saving a screenshot using [Selenium WebDriver](https://www.npmjs.com/package/selenium-webdriver)
If you've already got a base64-encoded string, you can prefix your mime type with `base64:` to indicate this. Here's an example of saving a screenshot using [Selenium WebDriver](https://www.npmjs.com/package/selenium-webdriver)
when a scenario fails:

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

After(function (testCase) {
var world = this;
After(async function (testCase) {
if (testCase.result.status === Status.FAILED) {
return webDriver.takeScreenshot().then(function(screenShot) {
world.attach(screenShot, 'base64:image/png');
});
const screenshot = await driver.takeScreenshot()
this.attach(screenshot, { mediaType: 'base64:image/png' })
}
});
})
```

Attachments are also printed by the progress, progress-bar and summary formatters.
Expand All @@ -100,15 +98,20 @@ It can be used to debug scenarios, especially in parallel mode.

```javascript
// Step definition
Given(/^a basic step$/, function() {
Given('a basic step', async function() {
this.attach('Some info.')
this.attach('{"some", "JSON"}}', 'application/json')
this.attach('{"some": "JSON"}}', { mediaType: 'application/json' })
this.attach((await driver.takeScreenshot()), {
mediaType: 'base64:image/png',
fileName: 'screenshot.png'
})
})

// Result format
// ✔ Given a basic step # path:line
// Attachment (text/plain): Some info.
// Attachment (application/json)
// Attachment (image/png): screenshot.png
```

## Logging
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@cucumber/compatibility-kit": "11.3.0",
"@cucumber/compatibility-kit": "^12.0.0",
"@cucumber/query": "12.0.1",
"@microsoft/api-documenter": "7.19.27",
"@microsoft/api-extractor": "7.33.7",
Expand Down
6 changes: 3 additions & 3 deletions src/formatter/helpers/issue_helpers_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ describe('IssueHelpers', () => {
${figures.tick} Given attachment step1 # steps.ts:35
Attachment (text/plain): Some info
Attachment (application/json)
Attachment (image/png)
${figures.cross} When attachment step2 # steps.ts:41
Attachment (image/png): screenshot.png
${figures.cross} When attachment step2 # steps.ts:44
Attachment (text/plain): Other info
error
- Then a passing step # steps.ts:29
Expand Down Expand Up @@ -290,7 +290,7 @@ describe('IssueHelpers', () => {
reindent(`
1) Scenario: my scenario # a.feature:2
${figures.tick} Given attachment step1 # steps.ts:35
${figures.cross} When attachment step2 # steps.ts:41
${figures.cross} When attachment step2 # steps.ts:44
error
- Then a passing step # steps.ts:29

Expand Down
9 changes: 7 additions & 2 deletions src/formatter/helpers/test_case_attempt_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,13 @@ function formatStep({
text += indentString(`${colorFn(argumentsText)}\n`, 4)
}
if (valueOrDefault(printAttachments, true)) {
attachments.forEach(({ body, mediaType }) => {
const message = mediaType === 'text/plain' ? `: ${body}` : ''
attachments.forEach(({ body, mediaType, fileName }) => {
let message = ''
if (mediaType === 'text/plain') {
message = `: ${body}`
} else if (fileName) {
message = `: ${fileName}`
}
text += indentString(`Attachment (${mediaType})${message}\n`, 4)
})
}
Expand Down
Loading