Skip to content

Commit

Permalink
feat: add support for flaky tests #4
Browse files Browse the repository at this point in the history
  • Loading branch information
estruyf committed Jul 17, 2024
1 parent 62545a9 commit e4425aa
Show file tree
Hide file tree
Showing 20 changed files with 387 additions and 161 deletions.
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig<{}, {}> = {
linkUrlOnFailure:
"https://github.com/playwright-community/playwright-msteams-reporter/issues",
linkTextOnFailure: "Report an issue",
mentionOnFailure: "Elio <[email protected]>, [email protected]",
mentionOnFailure: "[email protected]",
mentionOnFailureText: "",
debug: true,
},
Expand Down
1 change: 1 addition & 0 deletions src/constants/Images.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const Images = {
success: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAFCAIAAAAL5hHIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEklEQVQImWOweTCViYGBARkDAB59Abohe/o4AAAAAElFTkSuQmCC`,
flaky: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAFCAIAAAAL5hHIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVQImWP4nszDxMDAgIwBGnsBb7ZK8egAAAAASUVORK5CYII=`,
failed: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAFCAIAAAAL5hHIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVQImWP45+XJxMDAgIwBHUsBmph35dMAAAAASUVORK5CYII=`,
};
2 changes: 1 addition & 1 deletion src/models/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface TableCell {
style?: TableCellStyle;
}

export type TableCellStyle = "attention" | "good" | "warning";
export type TableCellStyle = "attention" | "good" | "warning" | "accent";

export interface TextBlock {
type: "TextBlock";
Expand Down
6 changes: 6 additions & 0 deletions src/models/TestStatuses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface TestStatuses {
passed: number;
flaky: number;
failed: number;
skipped: number;
}
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./AdaptiveCard";
export * from "./Table";
export * from "./TestStatuses";
export * from "./WebhookType";
87 changes: 81 additions & 6 deletions src/processResults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,45 @@ const DEFAULT_OPTIONS: MsTeamsReporterOptions = {
const SUITE_MOCK_PASSED = {
suites: [
{
allTests: () => [{ results: [{ status: "passed" }] }],
allTests: () => [{ outcome: () => "expected" }],
},
{
allTests: () => [
{ results: [{ status: "passed" }] },
{ results: [{ status: "passed" }] },
{ outcome: () => "expected" },
{ outcome: () => "expected" },
],
},
],
allTests: () => [{}, {}, {}],
};

const SUITE_MOCK_FLAKY = {
suites: [
{
allTests: () => [{ outcome: () => "expected" }],
},
{
allTests: () => [
{ outcome: () => "expected" },
{ outcome: () => "expected" },
],
},
{
allTests: () => [{ outcome: () => "flaky" }],
},
],
allTests: () => [{}, {}, {}],
};

const SUITE_MOCK_FAILED = {
suites: [
{
allTests: () => [{ results: [{ status: "failed" }] }],
allTests: () => [{ outcome: () => "unexpected" }],
},
{
allTests: () => [
{ results: [{ status: "passed" }] },
{ results: [{ status: "passed" }] },
{ outcome: () => "expected" },
{ outcome: () => "expected" },
],
},
],
Expand Down Expand Up @@ -165,6 +183,30 @@ describe("processResults", () => {
consoleErrorSpy.mockReset();
});

it("should include a flaky row", async () => {
const consoleLogSpy = jest
.spyOn(console, "log")
.mockImplementation((message) => {
if (message.includes("message") && message.includes("Flaky")) {
console.log(`Flaky`);
}
});
const fetchMock = jest
.fn()
.mockResolvedValue({ ok: true, text: () => "1" });
global.fetch = fetchMock;
const options: MsTeamsReporterOptions = {
...DEFAULT_OPTIONS,
webhookUrl: FLOW_WEBHOOK_URL,
webhookType: "powerautomate",
debug: true,
};
await processResults(SUITE_MOCK_FLAKY as any, options);
expect(consoleLogSpy).toHaveBeenCalledWith("Flaky");

consoleLogSpy.mockReset();
});

it("should use version 1.4 for adaptive card", async () => {
const consoleLogSpy = jest
.spyOn(console, "log")
Expand Down Expand Up @@ -277,6 +319,39 @@ describe("processResults", () => {
consoleLogSpy.mockReset();
});

it("should include the failure link", async () => {
const fakeFailureLink =
"https://github.com/estruyf/playwright-msteams-reporter";
const fakeFailureText = "View the failed tests";
const consoleLogSpy = jest
.spyOn(console, "log")
.mockImplementation((message) => {
if (
message.includes("message") &&
message.includes(fakeFailureLink) &&
message.includes(fakeFailureText)
) {
console.log(fakeFailureText);
}
});
const fetchMock = jest
.fn()
.mockResolvedValue({ ok: true, text: () => "1" });
global.fetch = fetchMock;
const options: MsTeamsReporterOptions = {
...DEFAULT_OPTIONS,
webhookUrl: FLOW_WEBHOOK_URL,
webhookType: "powerautomate",
linkUrlOnFailure: fakeFailureLink,
linkTextOnFailure: "View the failed tests",
debug: true,
};
await processResults(SUITE_MOCK_FAILED as any, options);
expect(consoleLogSpy).toHaveBeenCalledWith(fakeFailureText);

consoleLogSpy.mockReset();
});

it("should show debug message", async () => {
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
const fetchMock = jest
Expand Down
23 changes: 15 additions & 8 deletions src/processResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { MsTeamsReporterOptions } from ".";
import {
createTableRow,
getMentions,
getNotificationBackground,
getNotificationColor,
getNotificationTitle,
getTotalStatus,
validateWebhookUrl,
} from "./utils";
import { BaseAdaptiveCard, BaseTable, Images } from "./constants";
import { BaseAdaptiveCard, BaseTable } from "./constants";

export const processResults = async (
suite: Suite | undefined,
Expand All @@ -33,8 +36,7 @@ export const processResults = async (

const totalStatus = getTotalStatus(suite.suites);
const totalTests = suite.allTests().length;
const failedTests = totalStatus.failed + totalStatus.timedOut;
const isSuccess = failedTests === 0;
const isSuccess = totalStatus.failed === 0;

if (isSuccess && !options.notifyOnSuccess) {
if (!options.quiet) {
Expand All @@ -48,11 +50,16 @@ export const processResults = async (
table.rows.push(
createTableRow("Passed", totalStatus.passed, { style: "good" })
);
if (totalStatus.flaky) {
table.rows.push(
createTableRow("Flaky", totalStatus.flaky, { style: "warning" })
);
}
table.rows.push(
createTableRow("Failed", failedTests, { style: "attention" })
createTableRow("Failed", totalStatus.failed, { style: "attention" })
);
table.rows.push(
createTableRow("Skipped", totalStatus.skipped, { style: "warning" })
createTableRow("Skipped", totalStatus.skipped, { style: "accent" })
);
table.rows.push(
createTableRow("Total tests", totalTests, {
Expand All @@ -74,14 +81,14 @@ export const processResults = async (
type: "TextBlock",
size: "Large",
weight: "Bolder",
text: isSuccess ? "Tests passed" : "Tests failed",
color: isSuccess ? "Good" : "Attention",
text: getNotificationTitle(totalStatus),
color: getNotificationColor(totalStatus),
},
table,
] as any[],
bleed: true,
backgroundImage: {
url: isSuccess ? Images.success : Images.failed,
url: getNotificationBackground(totalStatus),
fillMode: "RepeatHorizontally",
},
};
Expand Down
44 changes: 44 additions & 0 deletions src/utils/getNotificationBackground.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { TestStatuses } from "../models";
import { getNotificationBackground } from ".";
import { Images } from "../constants";

describe("getNotificationBackground", () => {
it("Should return 'success' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 0,
flaky: 0,
skipped: 0,
};

const title = getNotificationBackground(statuses);

expect(title).toBe(Images.success);
});

it("Should return 'flaky' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 0,
flaky: 1,
skipped: 0,
};

const title = getNotificationBackground(statuses);

expect(title).toBe(Images.flaky);
});

it("Should return 'failed' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 1,
flaky: 1,
skipped: 0,
};

const title = getNotificationBackground(statuses);

expect(title).toBe(Images.failed);
});
});
17 changes: 17 additions & 0 deletions src/utils/getNotificationBackground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TestStatuses } from "../models";
import { getNotificationOutcome } from ".";
import { Images } from "../constants";

export const getNotificationBackground = (statuses: TestStatuses) => {
const outcome = getNotificationOutcome(statuses);

if (outcome === "passed") {
return Images.success;
}

if (outcome === "flaky") {
return Images.flaky;
}

return Images.failed;
};
43 changes: 43 additions & 0 deletions src/utils/getNotificationColor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TestStatuses } from "../models";
import { getNotificationColor } from ".";

describe("getNotificationColor", () => {
it("Should return 'good' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 0,
flaky: 0,
skipped: 0,
};

const title = getNotificationColor(statuses);

expect(title).toBe("Good");
});

it("Should return 'warning' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 0,
flaky: 1,
skipped: 0,
};

const title = getNotificationColor(statuses);

expect(title).toBe("Warning");
});

it("Should return 'attention' background", () => {
const statuses: TestStatuses = {
passed: 1,
failed: 1,
flaky: 1,
skipped: 0,
};

const title = getNotificationColor(statuses);

expect(title).toBe("Attention");
});
});
18 changes: 18 additions & 0 deletions src/utils/getNotificationColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TestStatuses } from "../models";
import { getNotificationOutcome } from ".";

export const getNotificationColor = (
statuses: TestStatuses
): "Good" | "Warning" | "Attention" => {
const outcome = getNotificationOutcome(statuses);

if (outcome === "passed") {
return "Good";
}

if (outcome === "flaky") {
return "Warning";
}

return "Attention";
};
18 changes: 18 additions & 0 deletions src/utils/getNotificationOutcome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TestStatuses } from "../models";

export const getNotificationOutcome = (
statuses: TestStatuses
): "passed" | "flaky" | "failed" => {
const isSuccess = statuses.failed === 0;
const hasFlakyTests = statuses.flaky > 0;

if (isSuccess && !hasFlakyTests) {
return "passed";
}

if (isSuccess && hasFlakyTests) {
return "flaky";
}

return "failed";
};
Loading

0 comments on commit e4425aa

Please sign in to comment.