Skip to content

Commit 15ae6d0

Browse files
committed
Allow handlers to be omitted
1 parent 190078d commit 15ae6d0

File tree

4 files changed

+219
-101
lines changed

4 files changed

+219
-101
lines changed

features/issues/705.feature

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# https://github.com/badeball/cypress-cucumber-preprocessor/issues/705
2+
3+
@no-default-plugin
4+
Feature: overriding event handlers
5+
Background:
6+
Given additional preprocessor configuration
7+
"""
8+
{
9+
"json": {
10+
"enabled": true
11+
}
12+
}
13+
"""
14+
15+
Scenario: overriding after:screenshot
16+
Given a file named "cypress/integration/a.feature" with:
17+
"""
18+
Feature: a feature
19+
Scenario: a scenario
20+
Given a failing step
21+
"""
22+
And a file named "cypress/support/step_definitions/steps.js" with:
23+
"""
24+
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
25+
Given("a failing step", function() {
26+
throw "some error"
27+
})
28+
"""
29+
And a file named "cypress/plugins/index.js" with:
30+
"""
31+
const { addCucumberPreprocessorPlugin, afterScreenshotHandler } = require("@badeball/cypress-cucumber-preprocessor");
32+
const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild");
33+
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor");
34+
35+
module.exports = async (on, config) => {
36+
await addCucumberPreprocessorPlugin(on, config, { omitAfterScreenshotHandler: true });
37+
38+
on("after:screenshot", (details) => afterScreenshotHandler(config, details))
39+
40+
on(
41+
"file:preprocessor",
42+
createBundler({
43+
plugins: [createEsbuildPlugin(config)]
44+
})
45+
);
46+
47+
return config;
48+
}
49+
"""
50+
When I run cypress
51+
Then it fails
52+
And the JSON report should contain an image attachment for what appears to be a screenshot

features/step_definitions/json_steps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Then(
111111
const embeddings: { data: string; mime_type: string }[] = actualJsonOutput
112112
.flatMap((feature: any) => feature.elements)
113113
.flatMap((element: any) => element.steps)
114-
.flatMap((step: any) => step.embeddings);
114+
.flatMap((step: any) => step.embeddings ?? []);
115115

116116
if (embeddings.length === 0) {
117117
throw new Error("Expected to find an embedding in JSON, but found none");

lib/add-cucumber-preprocessor-plugin.ts

Lines changed: 158 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -32,134 +32,193 @@ import { notNull } from "./type-guards";
3232

3333
import { getTags } from "./environment-helpers";
3434

35-
export default async function addCucumberPreprocessorPlugin(
36-
on: Cypress.PluginEvents,
37-
config: Cypress.PluginConfigOptions
38-
) {
39-
const preprocessor = await resolve();
35+
const preprocessorP = resolve();
36+
37+
let currentTestStepStartedId: string;
38+
let currentSpecMessages: messages.IEnvelope[];
39+
40+
export async function beforeRunHandler(config: Cypress.PluginConfigOptions) {
41+
const preprocessor = await preprocessorP;
42+
43+
if (!preprocessor.messages.enabled) {
44+
return;
45+
}
4046

4147
const messagesPath = path.join(
4248
config.projectRoot,
4349
preprocessor.messages.output
4450
);
4551

46-
const jsonPath = path.join(config.projectRoot, preprocessor.json.output);
52+
await fs.rm(messagesPath, { force: true });
53+
}
4754

48-
on("before:run", async () => {
49-
if (!preprocessor.messages.enabled) {
50-
return;
51-
}
55+
export async function afterRunHandler(config: Cypress.PluginConfigOptions) {
56+
const preprocessor = await preprocessorP;
5257

53-
await fs.rm(messagesPath, { force: true });
54-
});
58+
if (!preprocessor.messages.enabled) {
59+
return;
60+
}
5561

56-
on("after:run", async () => {
57-
if (!preprocessor.messages.enabled) {
58-
return;
59-
}
62+
const messagesPath = path.join(
63+
config.projectRoot,
64+
preprocessor.messages.output
65+
);
6066

61-
try {
62-
await fs.access(messagesPath, fsConstants.F_OK);
63-
} catch {
64-
return;
65-
}
67+
const jsonPath = path.join(config.projectRoot, preprocessor.json.output);
6668

67-
const messages = await fs.open(messagesPath, "r");
69+
try {
70+
await fs.access(messagesPath, fsConstants.F_OK);
71+
} catch {
72+
return;
73+
}
6874

69-
try {
70-
const json = await fs.open(jsonPath, "w");
75+
const messages = await fs.open(messagesPath, "r");
7176

72-
try {
73-
const child = child_process.spawn(preprocessor.json.formatter, {
74-
stdio: [messages.fd, json.fd, "inherit"],
75-
});
77+
try {
78+
const json = await fs.open(jsonPath, "w");
7679

77-
await new Promise<void>((resolve, reject) => {
78-
child.on("exit", (code) => {
79-
if (code === 0) {
80-
resolve();
81-
} else {
82-
reject(
83-
new Error(
84-
`${preprocessor.json.formatter} exited non-successfully`
85-
)
86-
);
87-
}
88-
});
89-
90-
child.on("error", reject);
80+
try {
81+
const child = child_process.spawn(preprocessor.json.formatter, {
82+
stdio: [messages.fd, json.fd, "inherit"],
83+
});
84+
85+
await new Promise<void>((resolve, reject) => {
86+
child.on("exit", (code) => {
87+
if (code === 0) {
88+
resolve();
89+
} else {
90+
reject(
91+
new Error(
92+
`${preprocessor.json.formatter} exited non-successfully`
93+
)
94+
);
95+
}
9196
});
92-
} finally {
93-
await json.close();
94-
}
97+
98+
child.on("error", reject);
99+
});
95100
} finally {
96-
await messages.close();
101+
await json.close();
97102
}
98-
});
103+
} finally {
104+
await messages.close();
105+
}
106+
}
99107

100-
let currentTestStepStartedId: string;
101-
let currentSpecMessages: messages.IEnvelope[];
108+
export async function beforeSpecHandler(config: Cypress.PluginConfigOptions) {
109+
currentSpecMessages = [];
110+
}
102111

103-
on("before:spec", () => {
104-
currentSpecMessages = [];
105-
});
112+
export async function afterSpecHandler(
113+
config: Cypress.PluginConfigOptions,
114+
spec: Cypress.Spec,
115+
results: CypressCommandLine.RunResult
116+
) {
117+
const preprocessor = await preprocessorP;
106118

107-
on("after:spec", async (_spec, results) => {
108-
// `results` is undefined when running via `cypress open`.
109-
if (!preprocessor.messages.enabled || !currentSpecMessages || !results) {
110-
return;
111-
}
119+
const messagesPath = path.join(
120+
config.projectRoot,
121+
preprocessor.messages.output
122+
);
123+
124+
// `results` is undefined when running via `cypress open`.
125+
if (!preprocessor.messages.enabled || !currentSpecMessages || !results) {
126+
return;
127+
}
112128

113-
const wasRemainingSkipped = results.tests.some((test) =>
114-
test.displayError?.match(HOOK_FAILURE_EXPR)
129+
const wasRemainingSkipped = results.tests.some((test) =>
130+
test.displayError?.match(HOOK_FAILURE_EXPR)
131+
);
132+
133+
if (wasRemainingSkipped) {
134+
console.log(
135+
chalk.yellow(
136+
` Hook failures can't be represented in JSON reports, thus none is created for ${spec.relative}.`
137+
)
115138
);
139+
} else {
140+
await fs.writeFile(
141+
messagesPath,
142+
currentSpecMessages.map((message) => JSON.stringify(message)).join("\n") +
143+
"\n",
144+
{
145+
flag: "a",
146+
}
147+
);
148+
}
149+
}
116150

117-
if (wasRemainingSkipped) {
118-
console.log(
119-
chalk.yellow(
120-
` Hook failures can't be represented in JSON reports, thus none is created for ${_spec.relative}.`
121-
)
122-
);
123-
} else {
124-
await fs.writeFile(
125-
messagesPath,
126-
currentSpecMessages
127-
.map((message) => JSON.stringify(message))
128-
.join("\n") + "\n",
129-
{
130-
flag: "a",
131-
}
132-
);
133-
}
134-
});
151+
export async function afterScreenshotHandler(
152+
config: Cypress.PluginConfigOptions,
153+
details: Cypress.ScreenshotDetails
154+
) {
155+
const preprocessor = await preprocessorP;
135156

136-
on("after:screenshot", async (details) => {
137-
if (!preprocessor.messages.enabled || !currentSpecMessages) {
138-
return details;
139-
}
157+
if (!preprocessor.messages.enabled || !currentSpecMessages) {
158+
return details;
159+
}
140160

141-
let buffer;
161+
let buffer;
142162

143-
try {
144-
buffer = await fs.readFile(details.path);
145-
} catch {
146-
return details;
147-
}
163+
try {
164+
buffer = await fs.readFile(details.path);
165+
} catch {
166+
return details;
167+
}
148168

149-
const message: messages.IEnvelope = {
150-
attachment: {
151-
testStepId: currentTestStepStartedId,
152-
body: buffer.toString("base64"),
153-
mediaType: "image/png",
154-
contentEncoding:
155-
"BASE64" as unknown as messages.Attachment.ContentEncoding,
156-
},
157-
};
169+
const message: messages.IEnvelope = {
170+
attachment: {
171+
testStepId: currentTestStepStartedId,
172+
body: buffer.toString("base64"),
173+
mediaType: "image/png",
174+
contentEncoding:
175+
"BASE64" as unknown as messages.Attachment.ContentEncoding,
176+
},
177+
};
158178

159-
currentSpecMessages.push(message);
179+
currentSpecMessages.push(message);
160180

161-
return details;
162-
});
181+
return details;
182+
}
183+
184+
type AddOptions = {
185+
omitBeforeRunHandler?: boolean;
186+
omitAfterRunHandler?: boolean;
187+
omitBeforeSpecHandler?: boolean;
188+
omitAfterSpecHandler?: boolean;
189+
omitAfterScreenshotHandler?: boolean;
190+
};
191+
192+
export default async function addCucumberPreprocessorPlugin(
193+
on: Cypress.PluginEvents,
194+
config: Cypress.PluginConfigOptions,
195+
options: AddOptions = {}
196+
) {
197+
const preprocessor = await preprocessorP;
198+
199+
if (!options.omitBeforeRunHandler) {
200+
on("before:run", () => beforeRunHandler(config));
201+
}
202+
203+
if (!options.omitAfterRunHandler) {
204+
on("after:run", () => afterRunHandler(config));
205+
}
206+
207+
if (!options.omitBeforeSpecHandler) {
208+
on("before:spec", () => beforeSpecHandler(config));
209+
}
210+
211+
if (!options.omitAfterSpecHandler) {
212+
on("after:spec", (spec, results) =>
213+
afterSpecHandler(config, spec, results)
214+
);
215+
}
216+
217+
if (!options.omitAfterScreenshotHandler) {
218+
on("after:screenshot", (details) =>
219+
afterScreenshotHandler(config, details)
220+
);
221+
}
163222

164223
on("task", {
165224
[TASK_APPEND_MESSAGES]: (messages: messages.IEnvelope[]) => {

lib/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ export { resolve as resolvePreprocessorConfiguration } from "./preprocessor-conf
2424

2525
export { getStepDefinitionPaths } from "./step-definitions";
2626

27-
export { default as addCucumberPreprocessorPlugin } from "./add-cucumber-preprocessor-plugin";
27+
export {
28+
default as addCucumberPreprocessorPlugin,
29+
beforeRunHandler,
30+
afterRunHandler,
31+
beforeSpecHandler,
32+
afterSpecHandler,
33+
afterScreenshotHandler,
34+
} from "./add-cucumber-preprocessor-plugin";
2835

2936
/**
3037
* Everything below exist merely for the purpose of being nice with TypeScript. All of these methods

0 commit comments

Comments
 (0)