Skip to content

Commit

Permalink
Invoke onAfterStep with a bunch of relevant data
Browse files Browse the repository at this point in the history
This relates to #1089 [1].

[1] #1089
  • Loading branch information
badeball committed Dec 9, 2023
1 parent 251f2c7 commit 0dba5ba
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.

- This is in line with how cucumber-js behaves.

- The [hook](docs/json-report.md#attachments-node-environment) for adding attachments from the Node environment, is invoked with a bunch of relevant data, relates to [#1089](https://github.com/badeball/cypress-cucumber-preprocessor/issues/1089).

## v19.2.0

- Add order option to all hooks, fixes [#481](https://github.com/badeball/cypress-cucumber-preprocessor/issues/481).
Expand Down
8 changes: 8 additions & 0 deletions docs/json-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,11 @@ await addCucumberPreprocessorPlugin(on, config, {
}
});
```

The hook is furthermore invoked with a bunch of other, relevant data, similar to `AfterStep(..)`, with the addition of a `result` property.

```ts
await addCucumberPreprocessorPlugin(on, config, {
onAfterStep({ pickle, pickleStep, gherkinDocument, testCaseStartedId, testStepId, result }) {}
});
```
129 changes: 129 additions & 0 deletions features/attachments_node.feature
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,135 @@ Feature: attachments
Then it passes
And there should be a JSON output similar to "fixtures/attachments/string.json"

Rule: it should be invoked with same arguments as AfterStep(), in addition to `results`

Scenario: etc arguments
Given a file named "setupNodeEvents.js" with:
"""
const assert = require("assert/strict");
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor");
const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor");
const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild");
module.exports = async (on, config) => {
await addCucumberPreprocessorPlugin(on, config, {
onAfterStep({ attach, pickle, pickleStep, gherkinDocument, testCaseStartedId, testStepId }) {
assert.equal(pickle.name, "a scenario name");
assert.equal(pickleStep.text, "a step");
assert.equal(gherkinDocument.feature.name, "a feature name");
assert.equal(typeof testCaseStartedId, "string");
assert.equal(typeof testStepId, "string");
attach("success");
}
});
on(
"file:preprocessor",
createBundler({
plugins: [createEsbuildPlugin(config)]
})
);
return config;
};
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature name
Scenario: a scenario name
Given a step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", function() {});
"""
When I run cypress
Then it passes
And there should be one attachment containing "success"

Scenario: passing step
Given a file named "setupNodeEvents.js" with:
"""
const assert = require("assert/strict");
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor");
const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor");
const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild");
module.exports = async (on, config) => {
await addCucumberPreprocessorPlugin(on, config, {
onAfterStep({ attach, result }) {
attach(result.status);
}
});
on(
"file:preprocessor",
createBundler({
plugins: [createEsbuildPlugin(config)]
})
);
return config;
};
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature name
Scenario: a scenario name
Given a step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", function() {});
"""
When I run cypress
Then it passes
And there should be one attachment containing "PASSED"

Scenario: failing step
Given additional Cypress configuration
"""
{
"screenshotOnRunFailure": false
}
"""
And a file named "setupNodeEvents.js" with:
"""
const assert = require("assert/strict");
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor");
const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor");
const { createEsbuildPlugin } = require("@badeball/cypress-cucumber-preprocessor/esbuild");
module.exports = async (on, config) => {
await addCucumberPreprocessorPlugin(on, config, {
onAfterStep({ attach, result }) {
attach(result.status);
}
});
on(
"file:preprocessor",
createBundler({
plugins: [createEsbuildPlugin(config)]
})
);
return config;
};
"""
And a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature name
Scenario: a scenario name
Given a step
"""
And a file named "cypress/support/step_definitions/steps.js" with:
"""
const { Given } = require("@badeball/cypress-cucumber-preprocessor");
Given("a step", function() {
throw "some error";
});
"""
When I run cypress
Then it fails
And there should be one attachment containing "FAILED"

Rule: it should correctly propogate a `wasLastStep` property regardless of test path

Background:
Expand Down
39 changes: 39 additions & 0 deletions features/step_definitions/json_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,45 @@ Then(
}
);

Then(
"there should be one attachment containing {string}",
async function (content) {
const absolutejsonPath = path.join(this.tmpDir, "cucumber-report.json");

const jsonFile = await fs.readFile(absolutejsonPath);

const actualJsonOutput = JSON.parse(jsonFile.toString());

const embeddings: { data: string; mime_type: string }[] = actualJsonOutput
.flatMap((feature: any) => feature.elements)
.flatMap((element: any) => element.steps)
.flatMap((step: any) => step.embeddings ?? []);

if (embeddings.length !== 1) {
throw new Error(
"Expected to find one embeddings in JSON, but found " +
embeddings.length
);
}

assert.strictEqual(
Buffer.from(embeddings[0].data, "base64").toString(),
content
);
}
);

await addCucumberPreprocessorPlugin(on, config, {
onAfterStep({
pickle,
pickleStep,
gherkinDocument,
testCaseStartedId,
testStepId,
result,
}) {},
});

Then("the JSON report should contain a spec", async function () {
const absolutejsonPath = path.join(this.tmpDir, "cucumber-report.json");

Expand Down
70 changes: 65 additions & 5 deletions lib/plugin-event-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import debug from "./helpers/debug";

import { createError } from "./helpers/error";

import { assertIsString } from "./helpers/assertions";
import { assertAndReturn, assertIsString } from "./helpers/assertions";

import {
createHtmlStream,
Expand All @@ -53,8 +53,12 @@ import {

import { useColors } from "./helpers/colors";

import { notNull } from "./helpers/type-guards";

import { version as packageVersion } from "./version";

import { IStepHookParameter } from "./public-member-types";

const resolve = memoize(origResolve);

interface PrettyDisabled {
Expand Down Expand Up @@ -623,10 +627,13 @@ export function testStepStartedHandler(

export type Attach = (data: string | Buffer, mediaType?: string) => void;

export type OnAfterStep = (options: {
wasLastStep: boolean;
attach: Attach;
}) => Promise<void> | void;
export type OnAfterStep = (
options: {
wasLastStep: boolean;
attach: Attach;
result: messages.TestStepResult;
} & IStepHookParameter
) => Promise<void> | void;

export async function testStepFinishedHandler(
config: Cypress.PluginConfigOptions,
Expand Down Expand Up @@ -656,8 +663,61 @@ export async function testStepFinishedHandler(

const attachments: ITaskCreateStringAttachment[] = [];

const { testCaseStartedId, testStepId } = testStepFinished;

const { testCaseId: pickleId } = assertAndReturn(
state.messages
.map((message) => message.testCaseStarted)
.filter(notNull)
.find((testCaseStarted) => testCaseStarted.id === testCaseStartedId),
"Expected to find a testCaseStarted"
);

const testCase = assertAndReturn(
state.messages
.map((message) => message.testCase)
.filter(notNull)
.find((testCase) => testCase.id === pickleId),
"Expected to find a testCase"
);

const pickleStepId = assertAndReturn(
assertAndReturn(
testCase.testSteps.find((testStep) => testStep.id === testStepId),
"Expected to find a testStep"
).pickleStepId,
"Expected testStep to have pickleStepId"
);

const pickle = assertAndReturn(
state.messages
.map((message) => message.pickle)
.filter(notNull)
.find((pickle) => pickle.id === pickleId),
"Expected to find a pickle"
);

const pickleStep = assertAndReturn(
pickle.steps.find((step) => step.id === pickleStepId),
"Expected to find a pickleStep"
);

const gherkinDocument = assertAndReturn(
state.messages
.map((message) => message.gherkinDocument)
.filter(notNull)
.find((gherkinDocument) => gherkinDocument.uri === pickle.uri),
"Expected to find a gherkinDocument"
);

await options.onAfterStep?.({
wasLastStep,
result: testStepFinished.testStepResult,
pickle,
pickleStep,
gherkinDocument,
testCaseStartedId,
testStepId,
attach(data, mediaType) {
if (typeof data === "string") {
mediaType = mediaType ?? "text/plain";
Expand Down

0 comments on commit 0dba5ba

Please sign in to comment.