Skip to content

Commit d64b46b

Browse files
authored
Merge branch 'main' into fix/update-padding-gas-fee-pill
2 parents 0950967 + 449dea1 commit d64b46b

File tree

96 files changed

+1790
-482
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1790
-482
lines changed

.github/scripts/create-e2e-test-report.ts

+133-129
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,17 @@
11
import * as fs from 'fs/promises';
2-
import humanizeDuration from 'humanize-duration';
32
import path from 'path';
4-
import * as xml2js from 'xml2js';
5-
6-
const XML = {
7-
parse: new xml2js.Parser().parseStringPromise,
8-
};
9-
10-
const humanizer = humanizeDuration.humanizer({
11-
language: 'shortEn',
12-
languages: {
13-
shortEn: {
14-
y: () => 'y',
15-
mo: () => 'mo',
16-
w: () => 'w',
17-
d: () => 'd',
18-
h: () => 'h',
19-
m: () => 'm',
20-
s: () => 's',
21-
ms: () => 'ms',
22-
},
23-
},
24-
delimiter: ' ',
25-
spacer: '',
26-
round: true,
27-
});
28-
29-
function formatTime(ms: number): string {
30-
if (ms < 1000) {
31-
return `${Math.round(ms)}ms`;
32-
}
33-
return humanizer(ms);
34-
}
35-
36-
/**
37-
* Replaces HTML `<strong>` tags with ANSI escape codes to format
38-
* text as bold in the console output.
39-
*/
40-
function consoleBold(str: string): string {
41-
return str
42-
.replaceAll('<strong>', '\x1b[1m')
43-
.replaceAll('</strong>', '\x1b[0m');
44-
}
45-
46-
interface TestRun {
47-
name: string;
48-
testSuites: TestSuite[];
49-
}
50-
51-
interface TestSuite {
52-
name: string;
53-
job: {
54-
name: string;
55-
id: string;
56-
};
57-
path: string;
58-
date: Date;
59-
tests: number;
60-
passed: number;
61-
failed: number;
62-
skipped: number;
63-
time: number;
64-
testCases: TestCase[];
65-
}
66-
67-
type TestCase =
68-
| {
69-
name: string;
70-
time: number;
71-
status: 'passed';
72-
}
73-
| {
74-
name: string;
75-
time: number;
76-
status: 'failed';
77-
error: string;
78-
};
3+
import type {
4+
TestCase,
5+
TestFile,
6+
TestRun,
7+
TestSuite,
8+
} from './shared/test-reports';
9+
import {
10+
consoleBold,
11+
formatTime,
12+
normalizeTestPath,
13+
XML,
14+
} from './shared/utils';
7915

8016
async function main() {
8117
const env = {
@@ -115,15 +51,16 @@ async function main() {
11551
'utf8',
11652
);
11753
const results = await XML.parse(file);
54+
11855
for (const suite of results.testsuites.testsuite || []) {
11956
if (!suite.testcase || !suite.$.file) continue;
12057
const tests = +suite.$.tests;
12158
const failed = +suite.$.failures;
12259
const skipped = tests - suite.testcase.length;
12360
const passed = tests - failed - skipped;
61+
12462
const testSuite: TestSuite = {
12563
name: suite.$.name,
126-
path: suite.$.file.slice(suite.$.file.indexOf(`test${path.sep}`)),
12764
job: {
12865
name: suite.properties?.[0].property?.[0]?.$.value ?? '',
12966
id: suite.properties?.[0].property?.[1]?.$.value ?? '',
@@ -134,8 +71,10 @@ async function main() {
13471
failed,
13572
skipped,
13673
time: +suite.$.time * 1000, // convert to ms,
74+
attempts: [],
13775
testCases: [],
13876
};
77+
13978
for (const test of suite.testcase || []) {
14079
const testCase: TestCase = {
14180
name: test.$.name,
@@ -145,63 +84,125 @@ async function main() {
14584
};
14685
testSuite.testCases.push(testCase);
14786
}
87+
88+
const testFile: TestFile = {
89+
path: normalizeTestPath(suite.$.file),
90+
tests: testSuite.tests,
91+
passed: testSuite.passed,
92+
failed: testSuite.failed,
93+
skipped: testSuite.skipped,
94+
time: testSuite.time,
95+
testSuites: [testSuite],
96+
};
97+
14898
const testRun: TestRun = {
14999
// regex to remove the shard number from the job name
150100
name: testSuite.job.name.replace(/\s+\(\d+\)$/, ''),
151-
testSuites: [testSuite],
101+
testFiles: [testFile],
152102
};
103+
153104
const existingRun = testRuns.find((run) => run.name === testRun.name);
154105
if (existingRun) {
155-
existingRun.testSuites.push(testSuite);
106+
const existingFile = existingRun.testFiles.find(
107+
(file) => file.path === testFile.path,
108+
);
109+
if (existingFile) {
110+
existingFile.testSuites.push(testSuite);
111+
} else {
112+
existingRun.testFiles.push(testFile);
113+
}
156114
} else {
157115
testRuns.push(testRun);
158116
}
159117
}
160118
}
161119

162120
for (const testRun of testRuns) {
163-
const deduped: { [path: string]: TestSuite } = {};
164-
for (const suite of testRun.testSuites) {
165-
const existing = deduped[suite.path];
166-
// If there is a duplicate, we keep the suite with the latest date
167-
if (!existing || existing.date < suite.date) {
168-
deduped[suite.path] = suite;
121+
for (const testFile of testRun.testFiles) {
122+
// Group test suites by name
123+
const suitesByName: Record<string, TestSuite[]> = {};
124+
125+
for (const suite of testFile.testSuites) {
126+
if (!suitesByName[suite.name]) {
127+
suitesByName[suite.name] = [];
128+
}
129+
suitesByName[suite.name].push(suite);
169130
}
170-
}
171131

172-
const suites = Object.values(deduped);
132+
// Determine the latest test suite by date and nest attempts
133+
const attempts: TestSuite[][] = [];
134+
for (const suites of Object.values(suitesByName)) {
135+
suites.sort((a, b) => b.date.getTime() - a.date.getTime()); // sort newest first
136+
const [latest, ...otherAttempts] = suites;
137+
latest.attempts = otherAttempts;
138+
attempts.push(otherAttempts);
139+
}
173140

174-
const title = `<strong>${testRun.name}</strong>`;
175-
console.log(consoleBold(title));
141+
// Remove the nested attempts from the top-level list
142+
const attemptSet = new Set(attempts.flat());
143+
testFile.testSuites = testFile.testSuites.filter(
144+
(suite) => !attemptSet.has(suite),
145+
);
176146

177-
if (suites.length) {
178-
const total = suites.reduce(
147+
const total = testFile.testSuites.reduce(
179148
(acc, suite) => ({
180149
tests: acc.tests + suite.tests,
181150
passed: acc.passed + suite.passed,
182151
failed: acc.failed + suite.failed,
183152
skipped: acc.skipped + suite.skipped,
153+
time: acc.time + suite.time,
184154
}),
185-
{ tests: 0, passed: 0, failed: 0, skipped: 0 },
155+
{ tests: 0, passed: 0, failed: 0, skipped: 0, time: 0 },
186156
);
187157

188-
core.summary.addRaw(
189-
total.failed ? `\n<details open>\n` : `\n<details>\n`,
158+
testFile.tests = total.tests;
159+
testFile.passed = total.passed;
160+
testFile.failed = total.failed;
161+
testFile.skipped = total.skipped;
162+
testFile.time = total.time;
163+
}
164+
165+
testRun.testFiles.sort((a, b) => a.path.localeCompare(b.path));
166+
167+
const title = `<strong>${testRun.name}</strong>`;
168+
169+
if (testRun.testFiles.length) {
170+
const total = testRun.testFiles.reduce(
171+
(acc, file) => ({
172+
tests: acc.tests + file.tests,
173+
passed: acc.passed + file.passed,
174+
failed: acc.failed + file.failed,
175+
skipped: acc.skipped + file.skipped,
176+
time: acc.time + file.time,
177+
}),
178+
{ tests: 0, passed: 0, failed: 0, skipped: 0, time: 0 },
190179
);
191-
core.summary.addRaw(`\n<summary>${title}</summary>\n`);
192180

193-
const times = suites.map((suite) => {
194-
const start = suite.date.getTime();
195-
const duration = suite.time;
196-
return { start, end: start + duration };
197-
});
181+
if (total.failed > 0) {
182+
if (testRun.name) console.log(`${consoleBold(title)} ❌`);
183+
core.summary.addRaw(`\n<details open>\n`);
184+
core.summary.addRaw(`\n<summary>${title} ❌</summary>\n`);
185+
} else {
186+
if (testRun.name) console.log(`${consoleBold(title)} ✅`);
187+
core.summary.addRaw(`\n<details>\n`);
188+
core.summary.addRaw(`\n<summary>${title} ✅</summary>\n`);
189+
}
190+
191+
const times = testRun.testFiles
192+
.map((file) =>
193+
file.testSuites.map((suite) => ({
194+
start: suite.date.getTime(),
195+
end: suite.date.getTime() + suite.time,
196+
})),
197+
)
198+
.flat();
198199
const earliestStart = Math.min(...times.map((t) => t.start));
199200
const latestEnd = Math.max(...times.map((t) => t.end));
200201
const executionTime = latestEnd - earliestStart;
201202

202-
const conclusion = `<strong>${
203-
total.tests
204-
}</strong> tests were completed in <strong>${formatTime(
203+
const conclusion = `<strong>${total.tests}</strong> ${
204+
total.tests === 1 ? 'test was' : 'tests were'
205+
} completed in <strong>${formatTime(
205206
executionTime,
206207
)}</strong> with <strong>${total.passed}</strong> passed, <strong>${
207208
total.failed
@@ -216,37 +217,40 @@ async function main() {
216217
core.summary.addRaw(
217218
`\n<hr style="height: 1px; margin-top: -5px; margin-bottom: 10px;">\n`,
218219
);
219-
for (const suite of suites) {
220-
if (!suite.failed) continue;
221-
console.error(suite.path);
220+
for (const file of testRun.testFiles) {
221+
if (file.failed === 0) continue;
222+
console.error(file.path);
222223
core.summary.addRaw(
223-
`\n#### [${suite.path}](https://github.com/${env.OWNER}/${env.REPOSITORY}/blob/${env.BRANCH}/${suite.path})\n`,
224+
`\n#### [${file.path}](https://github.com/${env.OWNER}/${env.REPOSITORY}/blob/${env.BRANCH}/${file.path})\n`,
224225
);
225-
if (suite.job.name && suite.job.id && env.RUN_ID) {
226-
core.summary.addRaw(
227-
`\n##### Job: [${suite.job.name}](https://github.com/${
228-
env.OWNER
229-
}/${env.REPOSITORY}/actions/runs/${env.RUN_ID}/job/${
230-
suite.job.id
231-
}${env.PR_NUMBER ? `?pr=${env.PR_NUMBER}` : ''})\n`,
232-
);
233-
}
234-
for (const test of suite.testCases) {
235-
if (test.status !== 'failed') continue;
236-
console.error(` ${test.name}`);
237-
console.error(` ${test.error}\n`);
238-
core.summary.addRaw(`\n##### ${test.name}\n`);
239-
core.summary.addRaw(`\n\`\`\`js\n${test.error}\n\`\`\`\n`);
226+
for (const suite of file.testSuites) {
227+
if (suite.failed === 0) continue;
228+
if (suite.job.name && suite.job.id && env.RUN_ID) {
229+
core.summary.addRaw(
230+
`\n##### Job: [${suite.job.name}](https://github.com/${
231+
env.OWNER
232+
}/${env.REPOSITORY}/actions/runs/${env.RUN_ID}/job/${
233+
suite.job.id
234+
}${env.PR_NUMBER ? `?pr=${env.PR_NUMBER}` : ''})\n`,
235+
);
236+
}
237+
for (const test of suite.testCases) {
238+
if (test.status !== 'failed') continue;
239+
console.error(` ${test.name}`);
240+
console.error(` ${test.error}\n`);
241+
core.summary.addRaw(`\n##### ${test.name}\n`);
242+
core.summary.addRaw(`\n\`\`\`js\n${test.error}\n\`\`\`\n`);
243+
}
240244
}
241245
}
242246
}
243247

244-
const rows = suites.map((suite) => ({
245-
'Test suite': suite.path,
246-
Passed: suite.passed ? `${suite.passed} ✅` : '',
247-
Failed: suite.failed ? `${suite.failed} ❌` : '',
248-
Skipped: suite.skipped ? `${suite.skipped} ⏩` : '',
249-
Time: formatTime(suite.time),
248+
const rows = testRun.testFiles.map((file) => ({
249+
'Test file': file.path,
250+
Passed: file.passed ? `${file.passed} ✅` : '',
251+
Failed: file.failed ? `${file.failed} ❌` : '',
252+
Skipped: file.skipped ? `${file.skipped} ⏩` : '',
253+
Time: formatTime(file.time),
250254
}));
251255

252256
const columns = Object.keys(rows[0]);
@@ -256,7 +260,7 @@ async function main() {
256260
.map((row) => {
257261
const data = {
258262
...row,
259-
'Test suite': `[${row['Test suite']}](https://github.com/${env.OWNER}/${env.REPOSITORY}/blob/${env.BRANCH}/${row['Test suite']})`,
263+
'Test file': `[${row['Test file']}](https://github.com/${env.OWNER}/${env.REPOSITORY}/blob/${env.BRANCH}/${row['Test file']})`,
260264
};
261265
return `| ${Object.values(data).join(' | ')} |`;
262266
})
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export interface TestRun {
2+
name: string;
3+
testFiles: TestFile[];
4+
}
5+
6+
export interface TestFile {
7+
path: string;
8+
tests: number;
9+
passed: number;
10+
failed: number;
11+
skipped: number;
12+
time: number;
13+
testSuites: TestSuite[];
14+
}
15+
16+
export interface TestSuite {
17+
name: string;
18+
job: {
19+
name: string;
20+
id: string;
21+
};
22+
date: Date;
23+
tests: number;
24+
passed: number;
25+
failed: number;
26+
skipped: number;
27+
time: number;
28+
attempts: TestSuite[];
29+
testCases: TestCase[];
30+
}
31+
32+
export type TestCase =
33+
| {
34+
name: string;
35+
time: number;
36+
status: 'passed';
37+
}
38+
| {
39+
name: string;
40+
time: number;
41+
status: 'failed';
42+
error: string;
43+
};

0 commit comments

Comments
 (0)