Skip to content

Commit 1baaf77

Browse files
committed
Add rudimentary support for videos in reports
These are currently inlined. Furthermore, these are not available in JSON reports. This fixes #1139 [1]. [1] #1139
1 parent bf45772 commit 1baaf77

File tree

9 files changed

+261
-1
lines changed

9 files changed

+261
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ Other changes:
2828

2929
- Remove use of patch-package in development mode, which was causing some issues, closes [#1255](https://github.com/badeball/cypress-cucumber-preprocessor/pull/1255).
3030

31+
- Add rudimentary support for videos in reports (messages & HTML), fixes [#1139](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1139).
32+
33+
- This can be enabled by setting `attachments.addVideos` to `true`.
34+
35+
- Videos, as with screenshots, are base64-encoded inline within the reports, making them bigger. I suspect size might be an issue for large attachments and this is why I consider this to be rudimentary to begin with. As issues arise, options to externalize attachments might be considered.
36+
3137
## v23.2.1
3238

3339
- Determine interactive mode correctly, fixes [#1323](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1323).

features/reporters/html.feature

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,50 @@ Feature: html report
4141
Then it passes
4242
And the report should display when last run
4343

44+
Scenario: videos
45+
Given additional Cypress configuration
46+
"""
47+
{
48+
"e2e": {
49+
"video": true
50+
}
51+
}
52+
"""
53+
And additional preprocessor configuration
54+
"""
55+
{
56+
"attachments": {
57+
"addVideos": true
58+
}
59+
}
60+
"""
61+
And a file named "cypress/e2e/duckduckgo.feature" with:
62+
"""
63+
Feature: duckduckgo.com
64+
Scenario: visiting the frontpage
65+
When I visit duckduckgo.com
66+
Then I should see a search bar
67+
"""
68+
And a file named "cypress/support/step_definitions/steps.js" with:
69+
"""
70+
import { When, Then } from "@badeball/cypress-cucumber-preprocessor";
71+
When("I visit duckduckgo.com", () => {
72+
cy.visit("https://duckduckgo.com/");
73+
});
74+
Then("I should see a search bar", () => {
75+
cy.get("input[type=text]")
76+
.should("have.attr", "placeholder")
77+
.and(
78+
"match",
79+
/Search the web without being tracked|Search without being tracked/,
80+
);
81+
});
82+
83+
"""
84+
When I run cypress
85+
Then it passes
86+
And the report should have a video attachment
87+
4488
Rule: it should obey `omitFiltered`
4589
Background:
4690
Given additional preprocessor configuration

features/reporters/messages.feature

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,3 +757,55 @@ Feature: messages report
757757
When I run cypress
758758
Then it passes
759759
And there should be a messages similar to "fixtures/multiple-scenarios-reloaded.ndjson"
760+
761+
Rule: it should allow for videos to be attached
762+
763+
Background:
764+
Given additional Cypress configuration
765+
"""
766+
{
767+
"e2e": {
768+
"video": true
769+
}
770+
}
771+
"""
772+
773+
Scenario: with addVideos disabled (default)
774+
Given a file named "cypress/e2e/a.feature" with:
775+
"""
776+
Feature: a feature
777+
Scenario: a scenario
778+
Given a step
779+
"""
780+
And a file named "cypress/support/step_definitions/steps.js" with:
781+
"""
782+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
783+
Given("a step", function() {});
784+
"""
785+
When I run cypress
786+
Then it passes
787+
And there should be a messages similar to "fixtures/passed-example.ndjson"
788+
789+
Scenario: with addVideos enabled
790+
Given additional preprocessor configuration
791+
"""
792+
{
793+
"attachments": {
794+
"addVideos": true
795+
}
796+
}
797+
"""
798+
And a file named "cypress/e2e/a.feature" with:
799+
"""
800+
Feature: a feature
801+
Scenario: a scenario
802+
Given a step
803+
"""
804+
And a file named "cypress/support/step_definitions/steps.js" with:
805+
"""
806+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
807+
Given("a step", function() {});
808+
"""
809+
When I run cypress
810+
Then it passes
811+
And the messages report should contain a video attachment

features/step_definitions/html_steps.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ Then(
9898
},
9999
);
100100

101+
Then(
102+
"the report should have a video attachment",
103+
async function (this: ICustomWorld) {
104+
const dom = await JSDOM.fromFile(
105+
path.join(this.tmpDir, "cucumber-report.html"),
106+
{ runScripts: "dangerously" },
107+
);
108+
109+
const el = await findByText(
110+
dom.window.document.documentElement,
111+
/\w+\.feature\.mp4/,
112+
);
113+
114+
assert(el);
115+
},
116+
);
117+
101118
/**
102119
* This is rather fudgy, due to number of X steps no longer being displayed in the reports after
103120
* a major refactor of @cucumber/react-components.

features/step_definitions/messages_steps.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,23 @@ Then(
103103
},
104104
);
105105

106+
Then(
107+
"the messages report should contain no attachments",
108+
async function (this: ICustomWorld) {
109+
const messages = await readMessagesReport(this.tmpDir);
110+
111+
const attachments: messages.Attachment[] = messages
112+
.map((m) => m.attachment)
113+
.filter(notEmpty);
114+
115+
if (attachments.length > 0) {
116+
throw new Error(
117+
`Expected to find np attachments, but found ${attachments.length}`,
118+
);
119+
}
120+
},
121+
);
122+
106123
Then(
107124
"the messages report should contain an image attachment for what appears to be a screenshot",
108125
async function (this: ICustomWorld) {
@@ -149,6 +166,29 @@ Then(
149166
},
150167
);
151168

169+
Then(
170+
"the messages report should contain a video attachment",
171+
async function (this: ICustomWorld) {
172+
const messages = await readMessagesReport(this.tmpDir);
173+
174+
const attachments: messages.Attachment[] = messages
175+
.map((m) => m.attachment)
176+
.filter(notEmpty);
177+
178+
if (attachments.length === 0) {
179+
throw new Error("Expected to find an attachment, but found none");
180+
} else if (attachments.length > 1) {
181+
throw new Error(
182+
"Expected to find a single attachment, but found " + attachments.length,
183+
);
184+
}
185+
186+
const [attachment] = attachments;
187+
188+
assert.strictEqual(attachment.mediaType, "video/mp4");
189+
},
190+
);
191+
152192
Then(
153193
"the messages report shouldn't contain any specs",
154194
async function (this: ICustomWorld) {

lib/plugin-event-handlers.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import detectCiEnvironment from "@cucumber/ci-environment";
1010
import * as messages from "@cucumber/messages";
1111
import chalk from "chalk";
1212
import split from "split";
13+
import { v4 as uuid } from "uuid";
1314

1415
import {
1516
ALL_HOOK_FAILURE_EXPR,
@@ -400,6 +401,79 @@ export async function afterRunHandler(
400401
throw createStateError("afterRunHandler", state.state);
401402
}
402403

404+
if (preprocessor.attachments.addVideos && "runs" in results) {
405+
const hasVideos = results.runs.some((run) => run.video !== null);
406+
407+
if (hasVideos) {
408+
const hookId = uuid();
409+
const testRunHookStartedId = uuid();
410+
const testRunStartedId = ensure(
411+
config.env["testRunStartedId"],
412+
"Expected to find a testRunStartedId",
413+
);
414+
415+
state.messages.accumulation.push(
416+
{
417+
hook: {
418+
id: hookId,
419+
type: messages.HookType.AFTER_TEST_RUN,
420+
name: "cypress-cucumber-preprocessor: Spec videos",
421+
sourceReference: {
422+
uri: "cypress-cucumber-preprocessor:internal",
423+
location: { line: 0 },
424+
},
425+
},
426+
},
427+
{
428+
testRunHookStarted: {
429+
id: testRunHookStartedId,
430+
hookId,
431+
testRunStartedId,
432+
timestamp: createTimestamp(),
433+
},
434+
},
435+
);
436+
437+
for (const run of results.runs) {
438+
if (!run.video) {
439+
continue;
440+
}
441+
442+
const filePath = path.relative(
443+
config.projectRoot,
444+
path.join(
445+
preprocessor.implicitIntegrationFolder,
446+
path.relative(config.videosFolder, run.video),
447+
),
448+
);
449+
450+
state.messages.accumulation.push({
451+
attachment: {
452+
testRunHookStartedId,
453+
body: await fs.readFile(run.video, { encoding: "base64" }),
454+
fileName: filePath,
455+
contentEncoding: messages.AttachmentContentEncoding.BASE64,
456+
mediaType: "video/mp4",
457+
},
458+
});
459+
}
460+
461+
state.messages.accumulation.push({
462+
testRunHookFinished: {
463+
testRunHookStartedId,
464+
result: {
465+
duration: {
466+
seconds: 0,
467+
nanos: 0,
468+
},
469+
status: messages.TestStepResultStatus.PASSED,
470+
},
471+
timestamp: createTimestamp(),
472+
},
473+
});
474+
}
475+
}
476+
403477
const testRunFinished: messages.Envelope = {
404478
testRunFinished: {
405479
success: "totalFailed" in results ? results.totalFailed === 0 : false,

lib/preprocessor-configuration.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,25 @@ describe("resolve()", () => {
10071007
setValueFn,
10081008
});
10091009
});
1010+
1011+
describe("addVideos", () => {
1012+
const getValueFn = (
1013+
configuration: IPreprocessorConfiguration,
1014+
): boolean => configuration.attachments.addVideos;
1015+
1016+
const setValueFn = (
1017+
configuration: IBaseUserConfiguration,
1018+
value: boolean,
1019+
) => (configuration.attachments = { addVideos: value });
1020+
1021+
basicBooleanExample({
1022+
testingType,
1023+
default: false,
1024+
environmentKey: "attachmentsAddVideos",
1025+
getValueFn,
1026+
setValueFn,
1027+
});
1028+
});
10101029
});
10111030
});
10121031
}

lib/preprocessor-configuration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const EnvironmentOverrides = D.partial({
8080
omitFiltered: StringishToBoolean,
8181
dryRun: StringishToBoolean,
8282
attachmentsAddScreenshots: StringishToBoolean,
83+
attachmentsAddVideos: StringishToBoolean,
8384
});
8485

8586
type IEnvironmentOverrides = D.TypeOf<typeof EnvironmentOverrides>;
@@ -114,6 +115,7 @@ const BaseConfiguration = D.partial({
114115
dryRun: D.boolean,
115116
attachments: D.partial({
116117
addScreenshots: D.boolean,
118+
addVideos: D.boolean,
117119
}),
118120
});
119121

@@ -160,6 +162,7 @@ export interface IPreprocessorConfiguration {
160162
readonly dryRun: boolean;
161163
readonly attachments: {
162164
addScreenshots: boolean;
165+
addVideos: boolean;
163166
};
164167
}
165168

@@ -308,6 +311,11 @@ export function combineIntoConfiguration(
308311
specific?.attachments?.addScreenshots ??
309312
unspecific.attachments?.addScreenshots ??
310313
true,
314+
addVideos:
315+
overrides.attachmentsAddVideos ??
316+
specific?.attachments?.addVideos ??
317+
unspecific.attachments?.addVideos ??
318+
false,
311319
};
312320

313321
return {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"@cucumber/cucumber": "^12.0.0",
6767
"@cucumber/cucumber-expressions": "^18.0.0",
6868
"@cucumber/gherkin": "^37.0.0",
69-
"@cucumber/html-formatter": "^22.0.0",
69+
"@cucumber/html-formatter": "^22.2.0",
7070
"@cucumber/message-streams": "^4.0.1",
7171
"@cucumber/messages": "^31.0.0",
7272
"@cucumber/pretty-formatter": "^1.0.1",

0 commit comments

Comments
 (0)