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

Allow to attach Cypress videos for failed specs only #1049

Merged
merged 3 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/allure-cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,28 @@ module.exports = {
};
```

## Write video attachments for failed tests only

If you want to see Cypress videos only for failed or broken tests in your Allure report, you can use the `videoOnFailOnly` option:

```diff
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
// ...
e2e: {
+ video: true,
setupNodeEvents: (on, config) => {
allureCypress(on, {
+ videoOnFailOnly: true,
});

return config;
},
},
};
```

## Known issues

### Global hooks reporting
Expand Down
5 changes: 5 additions & 0 deletions packages/allure-cypress/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import type { Status, StatusDetails } from "allure-js-commons";
import type { RuntimeMessage } from "allure-js-commons/sdk";
import type { Config } from "allure-js-commons/sdk/reporter";

export const ALLURE_REPORT_SYSTEM_HOOK = "__allure_report_system_hook__";

export const ALLURE_REPORT_STEP_COMMAND = "__allure_report_step_command__";

export type AllureCypressConfig = Config & {
videoOnFailOnly?: boolean;
};

export type CypressTest = Mocha.Test & {
wallClockStartedAt?: Date;
hookName?: string;
Expand Down
18 changes: 13 additions & 5 deletions packages/allure-cypress/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
getSuiteLabels,
parseTestPlan,
} from "allure-js-commons/sdk/reporter";
import type { Config } from "allure-js-commons/sdk/reporter";
import type {
AllureCypressConfig,
CypressHookEndMessage,
CypressHookStartMessage,
CypressMessage,
Expand All @@ -23,10 +23,12 @@ export class AllureCypress {
messagesByAbsolutePath = new Map<string, CypressMessage[]>();
runContextByAbsolutePath = new Map<string, RunContextByAbsolutePath>();
globalHooksMessages: CypressMessage[] = [];
videoOnFailOnly: boolean = false;

constructor(config?: Config) {
const { resultsDir = "./allure-results", ...rest } = config || {};
constructor(config?: AllureCypressConfig) {
const { resultsDir = "./allure-results", videoOnFailOnly = false, ...rest } = config || {};

this.videoOnFailOnly = videoOnFailOnly;
this.allureRuntime = new ReporterRuntime({
writer: new FileSystemWriter({
resultsDir,
Expand Down Expand Up @@ -76,6 +78,12 @@ export class AllureCypress {
endSpec(spec: Cypress.Spec, cypressVideoPath?: string) {
const specMessages = this.messagesByAbsolutePath.get(spec.absolute) ?? [];
const runContext = this.runContextByAbsolutePath.get(spec.absolute)!;
const isSpecFailed = specMessages.some(
(message) =>
message.type === "cypress_test_end" &&
(message.data.status === Status.FAILED || message.data.status === Status.BROKEN),
);
const shouldVideoBeAttached = (!this.videoOnFailOnly || isSpecFailed) && cypressVideoPath;

specMessages.forEach((message, i) => {
// we add cypressTestId to messages where it's possible because the field is very useful to glue data
Expand Down Expand Up @@ -233,7 +241,7 @@ export class AllureCypress {
this.allureRuntime.applyRuntimeMessages(last(runContext.executables)!, [message] as RuntimeMessage[]);
});

if (cypressVideoPath) {
if (shouldVideoBeAttached) {
const fixtureUuid = this.allureRuntime.startFixture(runContext.scopes[0], "after", {
name: "Cypress video",
status: Status.PASSED,
Expand Down Expand Up @@ -277,7 +285,7 @@ export class AllureCypress {
}
}

export const allureCypress = (on: Cypress.PluginEvents, allureConfig?: Config) => {
export const allureCypress = (on: Cypress.PluginEvents, allureConfig?: AllureCypressConfig) => {
const allureCypressReporter = new AllureCypress(allureConfig);

allureCypressReporter.attachToCypress(on);
Expand Down
284 changes: 256 additions & 28 deletions packages/allure-cypress/test/spec/video.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { ContentType } from "allure-js-commons";
import { runCypressInlineTest } from "../utils.js";

it("attaches same video to each spec in a test", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {});
describe("write video for every test", () => {
it("attaches same video to each spec in a test", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {});

it("bar", () => {});
`,
"cypress.config.js": () =>
`
it("bar", () => {});
`,
"cypress.config.js": () =>
`
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
Expand All @@ -37,25 +38,252 @@ it("attaches same video to each spec in a test", async () => {
},
};
`,
});

expect(tests).toHaveLength(2);
expect(groups).toHaveLength(1);
expect(groups[0]).toEqual(
expect.objectContaining({
name: "Cypress video",
children: expect.arrayContaining([tests[0].uuid, tests[1].uuid]),
afters: [
expect.objectContaining({
name: "Cypress video",
attachments: [
expect.objectContaining({
name: "Cypress video",
type: ContentType.MP4,
}),
],
}),
],
}),
);
});
});

describe("write video for failed tests only", () => {
it("doesn't attach video for passed tests", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {});
`,
"cypress.config.js": () =>
`
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
e2e: {
baseUrl: "https://allurereport.org",
viewportWidth: 1240,
video: true,
setupNodeEvents: (on, config) => {
allureCypress(on, {
videoOnFailOnly: true,
links: [
{
type: "issue",
urlTemplate: "https://allurereport.org/issues/%s"
},
{
type: "tms",
urlTemplate: "https://allurereport.org/tasks/%s"
},
]
});

return config;
},
},
};
`,
});

expect(tests).toHaveLength(1);
expect(groups).toHaveLength(0);
});

expect(tests).toHaveLength(2);
expect(groups).toHaveLength(1);
expect(groups[0]).toEqual(
expect.objectContaining({
name: "Cypress video",
children: expect.arrayContaining([tests[0].uuid, tests[1].uuid]),
afters: [
expect.objectContaining({
name: "Cypress video",
attachments: [
expect.objectContaining({
name: "Cypress video",
type: ContentType.MP4,
}),
],
}),
],
}),
);
it("attaches video for failed tests", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {
cy.wrap(1).eq(2);
});
`,
"cypress.config.js": () =>
`
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
e2e: {
baseUrl: "https://allurereport.org",
viewportWidth: 1240,
video: true,
testTimeout: 500,
setupNodeEvents: (on, config) => {
allureCypress(on, {
videoOnFailOnly: true,
links: [
{
type: "issue",
urlTemplate: "https://allurereport.org/issues/%s"
},
{
type: "tms",
urlTemplate: "https://allurereport.org/tasks/%s"
},
]
});

return config;
},
},
};
`,
});

expect(tests).toHaveLength(1);
expect(groups).toHaveLength(1);
expect(groups[0]).toEqual(
expect.objectContaining({
name: "Cypress video",
children: expect.arrayContaining([tests[0].uuid]),
afters: [
expect.objectContaining({
name: "Cypress video",
attachments: [
expect.objectContaining({
name: "Cypress video",
type: ContentType.MP4,
}),
],
}),
],
}),
);
});

it("attaches video for broken tests", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {
throw new Error("foo");
});
`,
"cypress.config.js": () =>
`
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
e2e: {
baseUrl: "https://allurereport.org",
viewportWidth: 1240,
video: true,
testTimeout: 500,
setupNodeEvents: (on, config) => {
allureCypress(on, {
videoOnFailOnly: true,
links: [
{
type: "issue",
urlTemplate: "https://allurereport.org/issues/%s"
},
{
type: "tms",
urlTemplate: "https://allurereport.org/tasks/%s"
},
]
});

return config;
},
},
};
`,
});

expect(tests).toHaveLength(1);
expect(groups).toHaveLength(1);
expect(groups[0]).toEqual(
expect.objectContaining({
name: "Cypress video",
children: expect.arrayContaining([tests[0].uuid]),
afters: [
expect.objectContaining({
name: "Cypress video",
attachments: [
expect.objectContaining({
name: "Cypress video",
type: ContentType.MP4,
}),
],
}),
],
}),
);
});

it("attaches video for all tests in failed file", async () => {
const { tests, groups } = await runCypressInlineTest({
"cypress/e2e/sample.cy.js": () => `
it("foo", () => {
cy.wrap(1).eq(1);
});

it("bar", () => {
cy.wrap(1).eq(2);
});
`,
"cypress.config.js": () =>
`
const { allureCypress } = require("allure-cypress/reporter");

module.exports = {
e2e: {
baseUrl: "https://allurereport.org",
viewportWidth: 1240,
video: true,
testTimeout: 500,
setupNodeEvents: (on, config) => {
allureCypress(on, {
videoOnFailOnly: true,
links: [
{
type: "issue",
urlTemplate: "https://allurereport.org/issues/%s"
},
{
type: "tms",
urlTemplate: "https://allurereport.org/tasks/%s"
},
]
});

return config;
},
},
};
`,
});

expect(tests).toHaveLength(2);
expect(groups).toHaveLength(1);
expect(groups[0]).toEqual(
expect.objectContaining({
name: "Cypress video",
children: expect.arrayContaining([tests[0].uuid, tests[1].uuid]),
afters: [
expect.objectContaining({
name: "Cypress video",
attachments: [
expect.objectContaining({
name: "Cypress video",
type: ContentType.MP4,
}),
],
}),
],
}),
);
});
});
Loading