Skip to content

Commit a499168

Browse files
authored
chore: add html report for tests (#202)
* chore: add html report for tests * chore: fix build
1 parent 7e79ba7 commit a499168

File tree

5 files changed

+168
-0
lines changed

5 files changed

+168
-0
lines changed

.github/workflows/test-e2e.yml

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ jobs:
3636
matrix:
3737
group: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
3838

39+
outputs:
40+
tag: ${{ steps.next-release.outputs.tag }}
41+
3942
steps:
4043
- name: Get Latest Next.js Release
4144
id: next-release
@@ -167,3 +170,9 @@ jobs:
167170
if: success() || failure()
168171
run: |
169172
deno run -A tools/deno/junit2md.ts artifacts >> $GITHUB_STEP_SUMMARY
173+
174+
- name: Publish test site
175+
if: success() || failure()
176+
run: |
177+
deno run -A tools/deno/junit2html.ts artifacts ${{needs.e2e.outputs.tag}} > report/index.html
178+
npx --yes netlify-cli@latest deploy --prod --cwd report

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ edge-runtime/vendor
1212

1313
deno.lock
1414
.eslintcache
15+
/report/index.html
16+
.DS_Store

report/netlify.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
publish = "."

report/style.css

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
* {
2+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
3+
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
4+
}
5+
6+
.test-results {
7+
display: flex;
8+
flex-wrap: wrap;
9+
}
10+
11+
.test-results a {
12+
display: inline-block;
13+
width: 16px;
14+
height: 16px;
15+
margin: 1px;
16+
}
17+
18+
a[data-status='passed'] {
19+
background-color: #2ecc71;
20+
}
21+
22+
a[data-status='failed'] {
23+
background-color: #e74c3c;
24+
}

tools/deno/junit2html.ts

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expandGlob } from 'https://deno.land/std/fs/mod.ts'
2+
import { parse } from 'https://deno.land/x/xml/mod.ts'
3+
import { escape } from 'https://deno.land/[email protected]/html/mod.ts'
4+
5+
interface TestCase {
6+
'@classname': string
7+
'@name': string
8+
'@time': number
9+
'@file': string
10+
failure?: string
11+
}
12+
13+
interface TestSuite {
14+
'@name': string
15+
'@errors': number
16+
'@failures': number
17+
'@skipped': number
18+
'@timestamp': string
19+
'@time': number
20+
'@tests': number
21+
testcase: TestCase[]
22+
}
23+
24+
interface TestSuites {
25+
'@name': string
26+
'@tests': number
27+
'@failures': number
28+
'@errors': number
29+
'@time': number
30+
testsuite: TestSuite[]
31+
}
32+
33+
async function parseXMLFile(filePath: string): Promise<{ testsuites: TestSuites }> {
34+
const xmlContent = await Deno.readTextFile(filePath)
35+
return parse(xmlContent) as unknown as { testsuites: TestSuites }
36+
}
37+
38+
const suites: Array<{
39+
name: string
40+
tests: number
41+
failures: number
42+
skipped: number
43+
time: number
44+
}> = []
45+
46+
const testCount = {
47+
failed: 0,
48+
passed: 0,
49+
}
50+
51+
const icons = {
52+
failed: '❌',
53+
passed: '✅',
54+
}
55+
56+
const tag = Deno.args[1] ?? 'canary'
57+
58+
function junitToHTML(xmlData: { testsuites: TestSuites }) {
59+
if (!xmlData.testsuites) {
60+
return ''
61+
}
62+
let html = ``
63+
64+
const testSuites = Array.isArray(xmlData.testsuites.testsuite)
65+
? xmlData.testsuites.testsuite
66+
: [xmlData.testsuites.testsuite]
67+
68+
for (const suite of testSuites) {
69+
const testCases = Array.isArray(suite.testcase) ? suite.testcase : [suite.testcase]
70+
71+
html += testCases
72+
.map((testCase) => {
73+
if ('skipped' in testCase) {
74+
return ''
75+
}
76+
const status = testCase.failure ? 'failed' : 'passed'
77+
78+
testCount[status]++
79+
return `<a data-status="${escape(status)}" title="${icons[status]} ${escape(
80+
testCase['@name'],
81+
)}" href="https://github.com/vercel/next.js/tree/${tag}/${testCase['@file']}"></a>`
82+
})
83+
.join('')
84+
}
85+
86+
return html
87+
}
88+
89+
async function processJUnitFiles(directoryPath: string) {
90+
let markdown = `<div class="test-results">`
91+
for await (const file of expandGlob(`${directoryPath}/**/*.xml`)) {
92+
const xmlData = await parseXMLFile(file.path)
93+
markdown += junitToHTML(xmlData)
94+
}
95+
markdown += `</div>`
96+
return markdown
97+
}
98+
99+
// Get the directory path from the command-line arguments
100+
const directoryPath = Deno.args[0]
101+
102+
// Check if the directory path is provided
103+
if (!directoryPath) {
104+
console.error('Please provide a directory path.')
105+
Deno.exit(1)
106+
}
107+
108+
// Process the JUnit files in the provided directory
109+
const details = await processJUnitFiles(directoryPath)
110+
111+
const total = testCount['passed'] + testCount['failed']
112+
113+
console.log(`<!DOCTYPE html>
114+
<html lang="en">
115+
<head>
116+
<meta charset="UTF-8">
117+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
118+
<title>Are we Next yet?</title>
119+
<link rel="stylesheet" href="style.css">
120+
</head>
121+
<body>`)
122+
123+
console.log(`<h1>${((testCount['passed'] / total) * 100).toFixed(2)}% passing</h1>`)
124+
125+
console.log(
126+
`<p>${testCount['passed']} passed out of ${total} Next.js deploy tests. ${testCount['failed']} still to go</p>`,
127+
)
128+
129+
console.log(details)
130+
131+
console.log(`</body></html>`)

0 commit comments

Comments
 (0)