diff --git a/CHANGELOG.md b/CHANGELOG.md index e7cac51..c678e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [1.9.0] + +- [#17](https://github.com/estruyf/playwright-github-actions-reporter/issues/17): Added the ability to define which types of test results should be shown in the summary + ## [1.8.0] - Added `⏭️` icon for skipped tests diff --git a/README.md b/README.md index 2afd5b4..c3241c4 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,18 @@ The reporter supports the following configuration options: | showAnnotations | Show annotations from tests | `true` | | showTags | Show tags from tests | `true` | | showError | Show error message in summary | `false` | +| includeResults | Define which types of test results should be shown in the summary | `['pass', 'skipped', 'fail', 'flaky']` | | quiet | Do not show any output in the console | `false` | To use these option, you can update the reporter configuration: ```ts import { defineConfig } from '@playwright/test'; +import type { GitHubActionOptions } from '@estruyf/github-actions-reporter'; export default defineConfig({ reporter: [ - ['@estruyf/github-actions-reporter', { + ['@estruyf/github-actions-reporter', { title: 'My custom title', useDetails: true, showError: true diff --git a/package-lock.json b/package-lock.json index 0714a4b..bbf58cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@estruyf/github-actions-reporter", - "version": "1.8.0", + "version": "1.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@estruyf/github-actions-reporter", - "version": "1.8.0", + "version": "1.9.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.0", diff --git a/package.json b/package.json index ac441e9..7601f01 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "@estruyf/github-actions-reporter", - "version": "1.8.0", + "version": "1.9.0", "description": "GitHub Actions reporter for Playwright", "main": "dist/index.js", "scripts": { "build": "tsc", "watch": "tsc -w", "test": "NODE_ENV=development npx playwright test", - "test:local": "act -j testing -P ubuntu-latest=catthehacker/ubuntu:act-latest --container-architecture linux/amd64 --env GITHUB_STEP_SUMMARY='/dev/stdout'", "test:jest": "jest --coverage --coverageDirectory=../coverage" }, "author": "Elio Struyf ", diff --git a/playwright.config.ts b/playwright.config.ts index 27c8952..feaaf7b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,3 +1,4 @@ +import type { GitHubActionOptions } from "./src/models/GitHubActionOptions"; import { PlaywrightTestConfig, defineConfig, devices } from "@playwright/test"; const config: PlaywrightTestConfig<{}, {}> = { @@ -14,14 +15,33 @@ const config: PlaywrightTestConfig<{}, {}> = { // ["list"], [ "./src/index.ts", - { - title: "Reporter testing", + { + title: "Reporter (details: false, report: fail, flaky, skipped)", useDetails: false, showError: true, quiet: false, + includeResults: ["fail", "flaky", "skipped"], + }, + ], + [ + "./src/index.ts", + { + title: "Reporter (details: false, report: pass, skipped)", + useDetails: false, + showError: true, + quiet: false, + includeResults: ["pass", "skipped"], + }, + ], + [ + "./src/index.ts", + { + title: "Reporter (details: true, report: fail, flaky, skipped)", + useDetails: true, + quiet: true, + includeResults: ["fail", "flaky", "skipped"], }, ], - ["./src/index.ts", { useDetails: true, quiet: true }], ], use: { actionTimeout: 0, diff --git a/src/index.ts b/src/index.ts index 0891802..9914574 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,15 +8,7 @@ import type { TestResult, } from "@playwright/test/reporter"; import { processResults } from "./utils/processResults"; - -export interface GitHubActionOptions { - title?: string; - useDetails?: boolean; - showAnnotations: boolean; - showTags: boolean; - showError?: boolean; - quiet?: boolean; -} +import { GitHubActionOptions } from "./models"; class GitHubAction implements Reporter { private suite: Suite | undefined; @@ -39,6 +31,10 @@ class GitHubAction implements Reporter { this.options.showTags = true; } + if (typeof options.includeResults === "undefined") { + this.options.includeResults = ["fail", "flaky", "pass", "skipped"]; + } + if (process.env.NODE_ENV === "development") { console.log(`Using development mode`); console.log(`Options: ${JSON.stringify(this.options, null, 2)}`); diff --git a/src/models/DisplayLevel.ts b/src/models/DisplayLevel.ts new file mode 100644 index 0000000..de21e5c --- /dev/null +++ b/src/models/DisplayLevel.ts @@ -0,0 +1 @@ +export type DisplayLevel = "pass" | "skipped" | "fail" | "flaky"; diff --git a/src/models/GitHubActionOptions.ts b/src/models/GitHubActionOptions.ts new file mode 100644 index 0000000..f70d96f --- /dev/null +++ b/src/models/GitHubActionOptions.ts @@ -0,0 +1,11 @@ +import { DisplayLevel } from "."; + +export interface GitHubActionOptions { + title?: string; + useDetails?: boolean; + showAnnotations: boolean; + showTags: boolean; + showError?: boolean; + quiet?: boolean; + includeResults?: DisplayLevel[]; +} diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..be21bc2 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,2 @@ +export * from "./DisplayLevel"; +export * from "./GitHubActionOptions"; diff --git a/src/utils/getHtmlTable.test.ts b/src/utils/getHtmlTable.test.ts index e38f6bb..fda630b 100644 --- a/src/utils/getHtmlTable.test.ts +++ b/src/utils/getHtmlTable.test.ts @@ -1,5 +1,13 @@ +import { DisplayLevel } from "../models"; import { getHtmlTable } from "./getHtmlTable"; +const defaultDisplayLevel: DisplayLevel[] = [ + "pass", + "fail", + "flaky", + "skipped", +]; + describe("getHtmlTable", () => { it("should return the HTML table with error column", async () => { const tests: any = [ @@ -46,7 +54,13 @@ describe("getHtmlTable", () => { }, ]; - const result = await getHtmlTable(tests, false, false, true); + const result = await getHtmlTable( + tests, + false, + false, + true, + defaultDisplayLevel + ); const expected = `
@@ -86,11 +100,59 @@ describe("getHtmlTable", () => { `; - expect(result.trim()).toEqual(expected.trim()); + expect(result?.trim()).toEqual(expected.trim()); }); - it("should return an empty HTML table if tests is empty (without error column)", async () => { - const result = await getHtmlTable([], false, false, false); + it("should return the HTML table with error column (excluding passed tests)", async () => { + const tests: any = [ + { + title: "Test 1", + results: [ + { + status: "passed", + duration: 1000, + retry: 0, + error: null, + }, + ], + parent: { + title: "Parent Title", + }, + }, + { + title: "Test 2", + results: [ + { + status: "failed", + duration: 2000, + retry: 1, + error: { + message: "Test failed", + }, + }, + ], + parent: null, + }, + { + title: "Test 3", + results: [ + { + status: "failed", + duration: null, + retry: null, + error: { + message: "Test failed", + }, + }, + ], + }, + ]; + + const result = await getHtmlTable(tests, false, false, true, [ + "fail", + "flaky", + "skipped", + ]); const expected = `
@@ -101,37 +163,53 @@ describe("getHtmlTable", () => { Status Duration Retries +Error + +Test 2 +❌ Fail +2s +1 +Test failed + + +Test 3 +❌ Fail + + +Test failed + `; - expect(result.trim()).toEqual(expected.trim()); + expect(result?.trim()).toEqual(expected.trim()); }); - it("should return an empty HTML table if tests is empty (including error column)", async () => { - const result = await getHtmlTable([], false, false, true); + it("should not return a table when no tests are provided (without error column)", async () => { + const result = await getHtmlTable( + [], + false, + false, + false, + defaultDisplayLevel + ); - const expected = ` -
- - - - - - - - - - - - -
TestStatusDurationRetriesError
-`; + expect(result).toBeUndefined(); + }); + + it("should not return a table when no tests are provided (including error column)", async () => { + const result = await getHtmlTable( + [], + false, + false, + true, + defaultDisplayLevel + ); - expect(result.trim()).toEqual(expected.trim()); + expect(result).toBeUndefined(); }); it("should return the HTML table with annotations row", async () => { @@ -158,7 +236,13 @@ describe("getHtmlTable", () => { }, ]; - const result = await getHtmlTable(tests, true, false, false); + const result = await getHtmlTable( + tests, + true, + false, + false, + defaultDisplayLevel + ); const expected = `
@@ -184,7 +268,7 @@ describe("getHtmlTable", () => { `; - expect(result.trim()).toEqual(expected.trim()); + expect(result?.trim()).toEqual(expected.trim()); }); it("should return the HTML table with annotations and tags columns", async () => { @@ -226,7 +310,13 @@ describe("getHtmlTable", () => { }, ]; - const result = await getHtmlTable(tests, true, true, true); + const result = await getHtmlTable( + tests, + true, + true, + true, + defaultDisplayLevel + ); const expected = `
@@ -264,6 +354,6 @@ describe("getHtmlTable", () => { `; - expect(result.trim()).toEqual(expected.trim()); + expect(result?.trim()).toEqual(expected.trim()); }); }); diff --git a/src/utils/getHtmlTable.ts b/src/utils/getHtmlTable.ts index 9c3156b..9ab09cb 100644 --- a/src/utils/getHtmlTable.ts +++ b/src/utils/getHtmlTable.ts @@ -1,17 +1,20 @@ import { TestCase } from "@playwright/test/reporter"; import Convert from "ansi-to-html"; import { getTestStatus } from "./getTestStatus"; +import { getTestStatusIcon } from "./getTestStatusIcon"; import { getTestTitle } from "./getTestTitle"; import { getTestTags } from "./getTestTags"; import { getTestAnnotations } from "./getTestAnnotations"; import { getTestDuration } from "./getTestDuration"; +import { DisplayLevel } from "../models"; export const getHtmlTable = async ( tests: TestCase[], showAnnotations: boolean, showTags: boolean, - showError: boolean -): Promise => { + showError: boolean, + displayLevel: DisplayLevel[] +): Promise => { const convert = new Convert(); const content: string[] = []; @@ -34,9 +37,14 @@ export const getHtmlTable = async ( content.push(``); content.push(``); + const testRows: string[] = []; for (const test of tests) { // Get the last result const result = test.results[test.results.length - 1]; + const testStatus = getTestStatus(test, result); + if (!displayLevel.includes(testStatus.toLowerCase() as DisplayLevel)) { + continue; + } if (showAnnotations && test.annotations) { let colLength = 4; @@ -49,29 +57,40 @@ export const getHtmlTable = async ( const annotations = await getTestAnnotations(test); if (annotations) { - content.push(``); - content.push(`${annotations}`); - content.push(``); + testRows.push(``); + testRows.push(`${annotations}`); + testRows.push(``); } } - content.push(``); - content.push(`${getTestTitle(test)}`); - content.push(`${getTestStatus(test, result)}`); - content.push(`${getTestDuration(result)}`); - content.push(`${result?.retry || ""}`); + testRows.push(``); + testRows.push(`${getTestTitle(test)}`); + testRows.push( + `${getTestStatusIcon(test, result)} ${getTestStatus( + test, + result + )}` + ); + testRows.push(`${getTestDuration(result)}`); + testRows.push(`${result?.retry || ""}`); if (showTags) { - content.push(`${getTestTags(test)}`); + testRows.push(`${getTestTags(test)}`); } if (showError) { const error = result?.error?.message || ""; - content.push(`${convert.toHtml(error)}`); + testRows.push(`${convert.toHtml(error)}`); } - content.push(``); + testRows.push(``); + } + + if (testRows.length === 0) { + return; } + content.push(testRows.join("\n")); + content.push(``); content.push(``); diff --git a/src/utils/getSuiteStatusIcon.test.ts b/src/utils/getSuiteStatusIcon.test.ts new file mode 100644 index 0000000..e9181ca --- /dev/null +++ b/src/utils/getSuiteStatusIcon.test.ts @@ -0,0 +1,58 @@ +import { TestCase } from "@playwright/test/reporter"; +import { getSuiteStatusIcon } from "./getSuiteStatusIcon"; + +describe("getSuiteStatusIcon", () => { + it("should return ✅ if all tests have passed", () => { + const tests = [ + { results: [{ status: "passed" }], outcome: () => "expected" }, + ] as TestCase[]; + + const result = getSuiteStatusIcon(tests); + + expect(result).toBe("✅"); + }); + + it("should return ⏭️ if any test has been skipped", () => { + const tests = [ + { results: [{ status: "skipped" }], outcome: () => "expected" }, + ] as TestCase[]; + + const result = getSuiteStatusIcon(tests); + + expect(result).toBe("✅"); + }); + + it("should return ❌ if any test has failed, interrupted, or timed out", () => { + const tests = [ + { results: [{ status: "failed" }], outcome: () => "expected" }, + ] as TestCase[]; + + const result = getSuiteStatusIcon(tests); + + expect(result).toBe("❌"); + }); + + it("should return ❌ if no tests", () => { + const result = getSuiteStatusIcon(undefined as any); + + expect(result).toBe("❌"); + }); + + it("should return ❌ if there is no test status", () => { + const tests = [ + { results: [{}], outcome: () => "unexpected" }, + ] as TestCase[]; + + const result = getSuiteStatusIcon(tests); + + expect(result).toBe("❌"); + }); + + it("should return ⚠️ if any test is flaky", () => { + const tests = [{ results: [{}], outcome: () => "flaky" }] as TestCase[]; + + const result = getSuiteStatusIcon(tests); + + expect(result).toBe("⚠️"); + }); +}); diff --git a/src/utils/getSuiteStatusIcon.ts b/src/utils/getSuiteStatusIcon.ts new file mode 100644 index 0000000..025f655 --- /dev/null +++ b/src/utils/getSuiteStatusIcon.ts @@ -0,0 +1,30 @@ +import { TestCase } from "@playwright/test/reporter"; +import { getTestOutcome } from "./getTestOutcome"; + +export const getSuiteStatusIcon = (tests: TestCase[]) => { + if (!tests || tests.length === 0) { + return "❌"; + } + + const testOutcomes = tests.map((test) => { + const lastResult = test.results[test.results.length - 1]; + const outcome = test.outcome(); + if (outcome === "flaky") { + return "flaky"; + } + + return getTestOutcome(test, lastResult); + }); + + if ( + testOutcomes.includes("failed") || + testOutcomes.includes("interrupted") || + testOutcomes.includes("timedOut") + ) { + return "❌"; + } else if (testOutcomes.includes("flaky")) { + return "⚠️"; + } + + return "✅"; +}; diff --git a/src/utils/getTableRows.test.ts b/src/utils/getTableRows.test.ts index 9b5e211..cdf05fa 100644 --- a/src/utils/getTableRows.test.ts +++ b/src/utils/getTableRows.test.ts @@ -1,3 +1,4 @@ +import { DisplayLevel } from "../models"; import { getTableRows } from "./getTableRows"; const tableHeaders = [ @@ -19,6 +20,13 @@ const tableHeaders = [ }, ]; +const defaultDisplayLevel: DisplayLevel[] = [ + "pass", + "fail", + "flaky", + "skipped", +]; + describe("getTableRows", () => { it("should return the table rows with headers and data", async () => { const tests: any = [ @@ -52,7 +60,13 @@ describe("getTableRows", () => { }, ]; - const result = await getTableRows(tests, false, false, true); + const result = await getTableRows( + tests, + false, + false, + true, + defaultDisplayLevel + ); const clonedTableHeaders = Object.assign([], tableHeaders); clonedTableHeaders.push({ data: "Error", header: true }); @@ -77,6 +91,60 @@ describe("getTableRows", () => { expect(result).toEqual(expected); }); + it("should return the table rows with headers and data (excluding passed tests)", async () => { + const tests: any = [ + { + title: "Test 1", + results: [ + { + status: "passed", + duration: 1000, + retry: 0, + error: null, + }, + ], + parent: { + title: "Parent Title", + }, + }, + { + title: "Test 2", + results: [ + { + status: "failed", + duration: 2000, + retry: 1, + error: { + message: "Test failed", + }, + }, + ], + parent: null, + }, + ]; + + const result = await getTableRows(tests, false, false, true, [ + "fail", + "flaky", + "skipped", + ]); + const clonedTableHeaders = Object.assign([], tableHeaders); + clonedTableHeaders.push({ data: "Error", header: true }); + + const expected = [ + clonedTableHeaders, + [ + { data: "Test 2", header: false }, + { data: "❌ Fail", header: false }, + { data: "2s", header: false }, + { data: "1", header: false }, + { data: "Test failed", header: false }, + ], + ]; + + expect(result).toEqual(expected); + }); + it("should return the table rows without error column if showError is false", async () => { const tests: any = [ { @@ -105,7 +173,13 @@ describe("getTableRows", () => { }, ]; - const result = await getTableRows(tests, false, false, false); + const result = await getTableRows( + tests, + false, + false, + false, + defaultDisplayLevel + ); expect(result).toEqual([ tableHeaders, @@ -125,17 +199,29 @@ describe("getTableRows", () => { }); it("should return an empty array if tests is empty (without error header)", async () => { - const result = await getTableRows([], false, false, false); + const result = await getTableRows( + [], + false, + false, + false, + defaultDisplayLevel + ); - expect(result).toEqual([tableHeaders]); + expect(result).toEqual([]); }); it("should return an empty array if tests is empty (including error header)", async () => { - const result = await getTableRows([], false, false, true); + const result = await getTableRows( + [], + false, + false, + true, + defaultDisplayLevel + ); const clonedTableHeaders = Object.assign([], tableHeaders); clonedTableHeaders.push({ data: "Error", header: true }); - expect(result).toEqual([clonedTableHeaders]); + expect(result).toEqual([]); }); it("should return the table rows with annotations", async () => { @@ -171,7 +257,13 @@ describe("getTableRows", () => { }, ]; - const result = await getTableRows(tests, true, false, false); + const result = await getTableRows( + tests, + true, + false, + false, + defaultDisplayLevel + ); const expected = [ tableHeaders, @@ -240,7 +332,13 @@ describe("getTableRows", () => { }, ]; - const result = await getTableRows(tests, true, true, true); + const result = await getTableRows( + tests, + true, + true, + true, + defaultDisplayLevel + ); const clonedTableHeaders = Object.assign([], tableHeaders); clonedTableHeaders.push({ data: "Tags", header: true }); diff --git a/src/utils/getTableRows.ts b/src/utils/getTableRows.ts index fdd756d..b8a9f52 100644 --- a/src/utils/getTableRows.ts +++ b/src/utils/getTableRows.ts @@ -6,12 +6,15 @@ import { getTestTitle } from "./getTestTitle"; import { getTestTags } from "./getTestTags"; import { getTestAnnotations } from "./getTestAnnotations"; import { getTestDuration } from "./getTestDuration"; +import { getTestStatusIcon } from "./getTestStatusIcon"; +import { DisplayLevel } from "../models"; export const getTableRows = async ( tests: TestCase[], showAnnotations: boolean, showTags: boolean, - showError: boolean + showError: boolean, + displayLevel: DisplayLevel[] ): Promise => { const convert = new Convert(); @@ -48,12 +51,18 @@ export const getTableRows = async ( }); } - const tableRows: SummaryTableRow[] = [tableHeaders]; + const tableRows: SummaryTableRow[] = []; for (const test of tests) { // Get the last result const result = test.results[test.results.length - 1]; + // Check if the test should be shown + const testStatus = getTestStatus(test, result); + if (!displayLevel.includes(testStatus.toLowerCase() as DisplayLevel)) { + continue; + } + if (showAnnotations && test.annotations) { let colLength = 4; if (showTags) { @@ -81,7 +90,7 @@ export const getTableRows = async ( header: false, }, { - data: getTestStatus(test, result), + data: `${getTestStatusIcon(test, result)} ${testStatus}`, header: false, }, { @@ -112,5 +121,9 @@ export const getTableRows = async ( tableRows.push(tableRow); } - return tableRows; + if (tableRows.length === 0) { + return []; + } + + return [tableHeaders, ...tableRows]; }; diff --git a/src/utils/getTestStatus.test.ts b/src/utils/getTestStatus.test.ts index ca383fe..986a54c 100644 --- a/src/utils/getTestStatus.test.ts +++ b/src/utils/getTestStatus.test.ts @@ -1,7 +1,7 @@ import { getTestStatus } from "./getTestStatus"; describe("getTestStatus", () => { - it("should return '⚠️ Flaky' when test status is 'passed' and result retry is greater than 0", () => { + it("should return 'Flaky' when test status is 'passed' and result retry is greater than 0", () => { const test: any = { outcome: () => "expected", }; @@ -12,10 +12,10 @@ describe("getTestStatus", () => { const status = getTestStatus(test, result); - expect(status).toBe("⚠️ Flaky"); + expect(status).toBe("Flaky"); }); - it("should return '✅ Pass' when test status is 'passed' and result retry is 0", () => { + it("should return 'Pass' when test status is 'passed' and result retry is 0", () => { const test: any = { outcome: () => "expected", }; @@ -26,10 +26,10 @@ describe("getTestStatus", () => { const status = getTestStatus(test, result); - expect(status).toBe("✅ Pass"); + expect(status).toBe("Pass"); }); - it("should return '⏭️ Skipped' when test status is 'skipped'", () => { + it("should return 'Skipped' when test status is 'skipped'", () => { const test: any = { outcome: () => "expected", }; @@ -40,10 +40,10 @@ describe("getTestStatus", () => { const status = getTestStatus(test, result); - expect(status).toBe("⏭️ Skipped"); + expect(status).toBe("Skipped"); }); - it("should return '❌ Fail' when test status is not 'passed' or 'skipped'", () => { + it("should return 'Fail' when test status is not 'passed' or 'skipped'", () => { const test: any = { outcome: () => "unexpected", }; @@ -54,10 +54,10 @@ describe("getTestStatus", () => { const status = getTestStatus(test, result); - expect(status).toBe("❌ Fail"); + expect(status).toBe("Fail"); }); - it("should return '❌ Fail' when no test status is provided", () => { + it("should return 'Fail' when no test status is provided", () => { const test: any = { outcome: () => "unexpected", }; @@ -67,6 +67,6 @@ describe("getTestStatus", () => { const status = getTestStatus(test, result); - expect(status).toBe("❌ Fail"); + expect(status).toBe("Fail"); }); }); diff --git a/src/utils/getTestStatus.ts b/src/utils/getTestStatus.ts index b85c5b5..c7a1796 100644 --- a/src/utils/getTestStatus.ts +++ b/src/utils/getTestStatus.ts @@ -1,19 +1,22 @@ import { TestCase, TestResult } from "@playwright/test/reporter"; import { getTestOutcome } from "./getTestOutcome"; -export const getTestStatus = (test: TestCase, result: TestResult) => { +export const getTestStatus = ( + test: TestCase, + result: TestResult +): "Flaky" | "Pass" | "Skipped" | "Fail" | string => { let value = ""; let status = getTestOutcome(test, result); if (status === "passed" && result.retry > 0) { - value = `⚠️ Flaky`; + value = `Flaky`; } else if (status === "passed") { - value = "✅ Pass"; + value = "Pass"; } else if (status === "skipped") { - value = `⏭️ Skipped`; + value = `Skipped`; } else { - value = "❌ Fail"; + value = "Fail"; } return value; diff --git a/src/utils/getTestStatusIcon.test.ts b/src/utils/getTestStatusIcon.test.ts index 1ef5553..50c7425 100644 --- a/src/utils/getTestStatusIcon.test.ts +++ b/src/utils/getTestStatusIcon.test.ts @@ -1,58 +1,72 @@ -import { TestCase } from "@playwright/test/reporter"; import { getTestStatusIcon } from "./getTestStatusIcon"; describe("getTestStatusIcon", () => { - it("should return ✅ if all tests have passed", () => { - const tests = [ - { results: [{ status: "passed" }], outcome: () => "expected" }, - ] as TestCase[]; - - const result = getTestStatusIcon(tests); - - expect(result).toBe("✅"); + it("should return '⚠️' when test status is 'passed' and result retry is greater than 0", () => { + const test: any = { + outcome: () => "expected", + }; + const result: any = { + retry: 1, + status: "passed", + }; + + const status = getTestStatusIcon(test, result); + + expect(status).toBe("⚠️"); }); - it("should return ⏭️ if any test has been skipped", () => { - const tests = [ - { results: [{ status: "skipped" }], outcome: () => "expected" }, - ] as TestCase[]; + it("should return '✅' when test status is 'passed' and result retry is 0", () => { + const test: any = { + outcome: () => "expected", + }; + const result: any = { + retry: 0, + status: "passed", + }; - const result = getTestStatusIcon(tests); + const status = getTestStatusIcon(test, result); - expect(result).toBe("✅"); + expect(status).toBe("✅"); }); - it("should return ❌ if any test has failed, interrupted, or timed out", () => { - const tests = [ - { results: [{ status: "failed" }], outcome: () => "expected" }, - ] as TestCase[]; - - const result = getTestStatusIcon(tests); - - expect(result).toBe("❌"); - }); + it("should return '⏭️' when test status is 'skipped'", () => { + const test: any = { + outcome: () => "expected", + }; + const result: any = { + retry: 1, + status: "skipped", + }; - it("should return ❌ if no tests", () => { - const result = getTestStatusIcon(undefined as any); + const status = getTestStatusIcon(test, result); - expect(result).toBe("❌"); + expect(status).toBe("⏭️"); }); - it("should return ❌ if there is no test status", () => { - const tests = [ - { results: [{}], outcome: () => "unexpected" }, - ] as TestCase[]; + it("should return '❌' when test status is not 'passed' or 'skipped'", () => { + const test: any = { + outcome: () => "unexpected", + }; + const result: any = { + retry: 1, + status: "failed", + }; - const result = getTestStatusIcon(tests); + const status = getTestStatusIcon(test, result); - expect(result).toBe("❌"); + expect(status).toBe("❌"); }); - it("should return ⚠️ if any test is flaky", () => { - const tests = [{ results: [{}], outcome: () => "flaky" }] as TestCase[]; + it("should return '❌' when no test status is provided", () => { + const test: any = { + outcome: () => "unexpected", + }; + const result: any = { + retry: 1, + }; - const result = getTestStatusIcon(tests); + const status = getTestStatusIcon(test, result); - expect(result).toBe("⚠️"); + expect(status).toBe("❌"); }); }); diff --git a/src/utils/getTestStatusIcon.ts b/src/utils/getTestStatusIcon.ts index db40b8a..3b7b40d 100644 --- a/src/utils/getTestStatusIcon.ts +++ b/src/utils/getTestStatusIcon.ts @@ -1,30 +1,18 @@ -import { TestCase } from "@playwright/test/reporter"; -import { getTestOutcome } from "./getTestOutcome"; +import { TestCase, TestResult } from "@playwright/test/reporter"; +import { getTestStatus } from "./getTestStatus"; -export const getTestStatusIcon = (tests: TestCase[]) => { - if (!tests || tests.length === 0) { - return "❌"; - } - - const testOutcomes = tests.map((test) => { - const lastResult = test.results[test.results.length - 1]; - const outcome = test.outcome(); - if (outcome === "flaky") { - return "flaky"; - } - - return getTestOutcome(test, lastResult); - }); +export const getTestStatusIcon = (test: TestCase, result: TestResult) => { + let value = getTestStatus(test, result); - if ( - testOutcomes.includes("failed") || - testOutcomes.includes("interrupted") || - testOutcomes.includes("timedOut") - ) { - return "❌"; - } else if (testOutcomes.includes("flaky")) { - return "⚠️"; + if (value === "Flaky") { + value = `⚠️`; + } else if (value === "Pass") { + value = "✅"; + } else if (value === "Skipped") { + value = `⏭️`; + } else if (value === "Fail") { + value = "❌"; } - return "✅"; + return value; }; diff --git a/src/utils/processResults.ts b/src/utils/processResults.ts index 279ef38..d511f65 100644 --- a/src/utils/processResults.ts +++ b/src/utils/processResults.ts @@ -3,14 +3,14 @@ import { SUMMARY_ENV_VAR } from "@actions/core/lib/summary"; import { Suite } from "@playwright/test/reporter"; import { existsSync, unlinkSync, writeFileSync } from "fs"; import { basename, join } from "path"; -import { GitHubActionOptions } from ".."; import { getHtmlTable } from "./getHtmlTable"; -import { getTestStatusIcon } from "./getTestStatusIcon"; +import { getSuiteStatusIcon } from "./getSuiteStatusIcon"; import { getTableRows } from "./getTableRows"; import { getSummaryTitle } from "./getSummaryTitle"; import { getSummaryDetails } from "./getSummaryDetails"; import { getTestsPerFile } from "./getTestsPerFile"; import { getTestHeading } from "./getTestHeading"; +import { DisplayLevel, GitHubActionOptions } from "../models"; export const processResults = async ( suite: Suite | undefined, @@ -55,26 +55,34 @@ export const processResults = async ( tests[filePath], options.showAnnotations, options.showTags, - !!options.showError + !!options.showError, + options.includeResults as DisplayLevel[] ); + if (!content) { + continue; + } + // Check if there are any failed tests - const testStatusIcon = getTestStatusIcon(tests[filePath]); + const testStatusIcon = getSuiteStatusIcon(tests[filePath]); summary.addDetails( `${testStatusIcon} ${getTestHeading(fileName, os, project)}`, content ); } else { - summary.addHeading(getTestHeading(fileName, os, project), 2); - const tableRows = await getTableRows( tests[filePath], options.showAnnotations, options.showTags, - !!options.showError + !!options.showError, + options.includeResults as DisplayLevel[] ); - summary.addTable(tableRows); + + if (tableRows.length !== 0) { + summary.addHeading(getTestHeading(fileName, os, project), 2); + summary.addTable(tableRows); + } } } }