Repository: vercel/next.js
Pull request: [test] Add test suite name for Tests failures table

In [GH CI failure](https://github.com/vercel/next.js/actions/runs/21465646809/attempts/2), the failed test suites only show the file path, not the actual test name. You had to click the dropdown, scroll down, and find which test had failed. This PR adds the failed test name to the table.

<img width="1148" height="818" alt="CleanShot 2026-01-29 at 20 24 52@2x" src="https://github.com/user-attachments/assets/10ebff0e-8ad5-48c8-87b6-e8e300518123" />


It will look like:

# Tests failures                                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                                   
| Test Name | Test Path |                                                                                                                                                                                                                                                                                                        
|-----------|-----------|                                                                                                                                                                                                                                                                                                        
| app-dir static/dynamic handling > should do something | [test/e2e/app-dir/app-static/app-static.test.ts](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/app-static/app-static.test.ts) |                                                                                                                       
| app-dir static/dynamic handling > should do another thing | [test/e2e/app-dir/app-static/app-static.test.ts](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/app-static/app-static.test.ts) |                                                                                                                   
| resume-data-cache > should resume cache | [test/e2e/app-dir/resume-data-cache/resume-data-cache.test.ts](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/resume-data-cache/resume-data-cache.test.ts) |

Changed files:
filename: run-tests.js
patch: @@ -113,13 +113,14 @@ const mockTrace = () => ({
 
 // which types we have configured to run separate
 const configuredTestTypes = Object.values(testFilters)
+/** @type {Map<string, { output: string, failedCases: string[] }>} */
 const errorsPerTests = new Map()
 
 async function maybeLogSummary() {
   if (process.env.CI && errorsPerTests.size > 0) {
     const outputTemplate = `
 ${Array.from(errorsPerTests.entries())
-  .map(([test, output]) => {
+  .map(([test, { output }]) => {
     return `
 <details>
 <summary>${test}</summary>
@@ -133,20 +134,27 @@ ${output}
   })
   .join('\n')}`
 
+    // Build table rows with one row per failed test case
+    const tableRows = []
+    for (const [test, { failedCases }] of errorsPerTests.entries()) {
+      const testLink = `<a href="https://github.com/vercel/next.js/blob/canary/${test}">${test}</a>`
+      if (failedCases.length === 0) {
+        tableRows.push(['Unknown', testLink])
+      } else {
+        for (const caseName of failedCases) {
+          tableRows.push([caseName, testLink])
+        }
+      }
+    }
+
     await core.summary
       .addHeading('Tests failures')
       .addTable([
         [
-          {
-            data: 'Test suite',
-            header: true,
-          },
+          { data: 'Test Name', header: true },
+          { data: 'Test Path', header: true },
         ],
-        ...Array.from(errorsPerTests.entries()).map(([test]) => {
-          return [
-            `<a href="https://github.com/vercel/next.js/blob/canary/${test}">${test}</a>`,
-          ]
-        }),
+        ...tableRows,
       ])
       .addRaw(outputTemplate)
       .write()
@@ -608,7 +616,7 @@ ${ENDGROUP}`)
             }
 
             if (process.env.CI) {
-              errorsPerTests.set(test.file, output)
+              errorsPerTests.set(test.file, { output, failedCases: [] })
             }
 
             if (isExpanded) {
@@ -704,6 +712,28 @@ ${ENDGROUP}`)
           'utf8'
         )
         const obj = JSON.parse(testsOutput)
+
+        // Extract failed test case names from Jest JSON output
+        if (!passed && process.env.CI) {
+          const failedCases = []
+          for (const testResult of obj.testResults || []) {
+            for (const assertion of testResult.assertionResults || []) {
+              if (assertion.status === 'failed') {
+                const caseName = [
+                  ...(assertion.ancestorTitles || []),
+                  assertion.title,
+                ].join(' > ')
+                failedCases.push(caseName)
+              }
+            }
+          }
+          // Update errorsPerTests with failed case names
+          const existing = errorsPerTests.get(test.file)
+          if (existing) {
+            existing.failedCases = failedCases
+          }
+        }
+
         obj.processEnv = {
           NEXT_TEST_MODE: process.env.NEXT_TEST_MODE,
           HEADLESS: process.env.HEADLESS,