Repository: vercel/next.js
Pull request: [test] Assert on all errors in app/ hydration-error suite

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the PR.
- Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide

### Adding or Updating Examples

- The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to understand the PR)
- When linking to a Slack thread, you might want to share details of the conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->


Changed files:
filename: test/development/acceptance-app/hydration-error.test.ts
patch: @@ -3,7 +3,7 @@ import { createSandbox } from 'development-sandbox'
 import { FileRef, nextTestSetup } from 'e2e-utils'
 import path from 'path'
 import { outdent } from 'outdent'
-import { getRedboxTotalErrorCount, retry } from 'next-test-utils'
+import { getToastErrorCount, retry } from 'next-test-utils'
 
 describe('Error overlay for hydration errors in App router', () => {
   const { next, isTurbopack } = nextTestSetup({
@@ -69,32 +69,11 @@ describe('Error overlay for hydration errors in App router', () => {
       ])
     )
     const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
 
     // TODO(veil): Should be "rendered text"
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
-
-    expect(await session.getRedboxDescriptionWarning()).toMatchInlineSnapshot(`
-      "- A server/client branch \`if (typeof window !== 'undefined')\`.
-      - Variable input such as \`Date.now()\` or \`Math.random()\` which changes each time it's called.
-      - Date formatting in a user's locale which doesn't match the server.
-      - External changing data without sending a snapshot of it along with the HTML.
-      - Invalid HTML tag nesting.
-
-      It can also happen if the client has a browser extension installed which messes with the HTML before React loaded."
-    `)
-
-    expect(await session.getRedboxErrorLink()).toMatchInlineSnapshot(
-      `"See more info here: https://nextjs.org/docs/messages/react-hydration-error"`
-    )
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -111,8 +90,22 @@ describe('Error overlay for hydration errors in App router', () => {
                                    <main className="child">
      +                               client
      -                               server
-                             ..."
+                             ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (6:7) @ Mismatch
+     > 6 |       <main className="child">{isClient ? "client" : "server"}</main>
+         |       ^",
+       "stack": [
+         "main <anonymous> (0:0)",
+         "Mismatch app/page.js (6:7)",
+       ],
+     }
     `)
+    expect(await session.getRedboxErrorLink()).toMatchInlineSnapshot(
+      `"See more info here: https://nextjs.org/docs/messages/react-hydration-error"`
+    )
 
     await session.patch(
       'app/page.js',
@@ -153,14 +146,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -175,12 +165,19 @@ describe('Error overlay for hydration errors in App router', () => {
                                <Mismatch params={Promise} searchParams={Promise}>
                                  <div className="parent">
      +                             <main className="only">
-                             ..."
+                             ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (6:20) @ Mismatch
+     > 6 |       {isClient && <main className="only" />}
+         |                    ^",
+       "stack": [
+         "main <anonymous> (0:0)",
+         "Mismatch app/page.js (6:20)",
+       ],
+     }
     `)
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
   })
 
   it('should show correct hydration error when extra attributes set on server', async () => {
@@ -206,15 +203,12 @@ describe('Error overlay for hydration errors in App router', () => {
         ['app/page.js', `export default function Page() { return 'page' }`],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
     if (isTurbopack) {
-      expect(pseudoHtml).toMatchInlineSnapshot(`
-       "...
+      await expect(browser).toDisplayCollapsedRedbox(`
+       {
+         "componentStack": "...
            <HotReload assetPrefix="" globalError={[...]}>
              <AppDevOverlay state={{nextId:1, ...}} globalError={[...]}>
                <AppDevOverlayErrorBoundary globalError={[...]} onError={function bound dispatchSetState}>
@@ -234,11 +228,23 @@ describe('Error overlay for hydration errors in App router', () => {
        -                         className="server-html"
                                >
                            ...
-               ..."
+               ...",
+         "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/layout.js (5:5) @ Root
+       > 5 |     <html
+           |     ^",
+         "stack": [
+           "html <anonymous> (0:0)",
+           "Root app/layout.js (5:5)",
+         ],
+       }
       `)
     } else {
-      expect(pseudoHtml).toMatchInlineSnapshot(`
-       "...
+      await expect(browser).toDisplayCollapsedRedbox(`
+       {
+         "componentStack": "...
            <HotReload assetPrefix="" globalError={[...]}>
              <AppDevOverlay state={{nextId:1, ...}} globalError={[...]}>
                <AppDevOverlayErrorBoundary globalError={[...]} onError={function bound dispatchSetState}>
@@ -255,13 +261,20 @@ describe('Error overlay for hydration errors in App router', () => {
        -                         className="server-html"
                                >
                            ...
-               ..."
+               ...",
+         "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/layout.js (5:5) @ Root
+       > 5 |     <html
+           |     ^",
+         "stack": [
+           "html <anonymous> (0:0)",
+           "Root app/layout.js (5:5)",
+         ],
+       }
       `)
     }
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
   })
 
   it('should show correct hydration error when client renders an extra text node', async () => {
@@ -286,14 +299,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -311,12 +321,19 @@ describe('Error overlay for hydration errors in App router', () => {
      +                             second
      -                             <footer className="3">
                                    ...
-                             ..."
+                             ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (5:5) @ Mismatch
+     > 5 |     <div className="parent">
+         |     ^",
+       "stack": [
+         "div <anonymous> (0:0)",
+         "Mismatch app/page.js (5:5)",
+       ],
+     }
     `)
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
   })
 
   it('should show correct hydration error when server renders an extra element', async () => {
@@ -339,14 +356,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -361,12 +375,19 @@ describe('Error overlay for hydration errors in App router', () => {
                                <Mismatch params={Promise} searchParams={Promise}>
                                  <div className="parent">
      -                             <main className="only">
-                             ..."
+                             ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (5:5) @ Mismatch
+     > 5 |     <div className="parent">
+         |     ^",
+       "stack": [
+         "div <anonymous> (0:0)",
+         "Mismatch app/page.js (5:5)",
+       ],
+     }
     `)
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
   })
 
   it('should show correct hydration error when server renders an extra text node', async () => {
@@ -385,18 +406,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -411,7 +425,18 @@ describe('Error overlay for hydration errors in App router', () => {
                                <Mismatch params={Promise} searchParams={Promise}>
                                  <div className="parent">
      -                             only
-                             ..."
+                             ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (4:10) @ Mismatch
+     > 4 |   return <div className="parent">{!isClient && "only"}</div>;
+         |          ^",
+       "stack": [
+         "div <anonymous> (0:0)",
+         "Mismatch app/page.js (4:10)",
+       ],
+     }
     `)
   })
 
@@ -436,21 +461,16 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(2)
+      expect(await getToastErrorCount(browser)).toBe(2)
     })
 
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(`
-     "In HTML, text nodes cannot be a child of <tr>.
-     This will cause a hydration error."
-    `)
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "componentStack": "...
          <ScrollAndFocusHandler segmentPath={[...]}>
            <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
              <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
@@ -466,7 +486,48 @@ describe('Error overlay for hydration errors in App router', () => {
                                  <tbody>
                                    <tr>
      >                               test
-                           ..."
+                           ...",
+         "description": "In HTML, text nodes cannot be a child of <tr>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (6:9) @ Page
+     > 6 |         <tr>test</tr>
+         |         ^",
+         "stack": [
+           "tr <anonymous> (0:0)",
+           "Page app/page.js (6:9)",
+         ],
+       },
+       {
+         "componentStack": "...
+         <RenderFromTemplateContext>
+           <ScrollAndFocusHandler segmentPath={[...]}>
+             <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
+               <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
+                 <LoadingBoundary loading={null}>
+                   <HTTPAccessFallbackBoundary notFound={[...]} forbidden={undefined} unauthorized={undefined}>
+                     <HTTPAccessFallbackErrorBoundary pathname="/" notFound={[...]} forbidden={undefined} ...>
+                       <RedirectBoundary>
+                         <RedirectErrorBoundary router={{...}}>
+                           <InnerLayoutRouter url="/" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
+                             <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
+                               <Page params={Promise} searchParams={Promise}>
+     +                           <table>
+     -                           test
+                             ...",
+         "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+         "environmentLabel": null,
+         "label": "Runtime Error",
+         "source": "app/page.js (4:5) @ Page
+     > 4 |     <table>
+         |     ^",
+         "stack": [
+           "table <anonymous> (0:0)",
+           "Page app/page.js (4:5)",
+         ],
+       },
+     ]
     `)
   })
 
@@ -490,14 +551,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
-
-    expect(await getRedboxTotalErrorCount(browser)).toBe(1)
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -513,7 +571,19 @@ describe('Error overlay for hydration errors in App router', () => {
      >                           <table>
      >                             {" "}
                                    ...
-                             ..."
+                             ...",
+       "description": "In HTML, whitespace text nodes cannot be a child of <table>. Make sure you don't have any extra whitespace between tags on each line of your source code.
+     This will cause a hydration error.",
+       "environmentLabel": null,
+       "label": "Console Error",
+       "source": "app/page.js (4:5) @ Page
+     > 4 |     <table>
+         |     ^",
+       "stack": [
+         "table <anonymous> (0:0)",
+         "Page app/page.js (4:5)",
+       ],
+     }
     `)
   })
 
@@ -542,12 +612,11 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     {
+       "componentStack": "...
          <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
            <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
              <LoadingBoundary loading={null}>
@@ -564,12 +633,19 @@ describe('Error overlay for hydration errors in App router', () => {
      +                           <main className="second">
      -                           <footer className="3">
                                  ...
-                         ..."
+                         ...",
+       "description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
+       "environmentLabel": null,
+       "label": "Runtime Error",
+       "source": "app/page.js (9:22) @ Mismatch
+     >  9 |         {isClient && <main className="second" />}
+          |                      ^",
+       "stack": [
+         "main <anonymous> (0:0)",
+         "Mismatch app/page.js (9:22)",
+       ],
+     }
     `)
-
-    expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
-      `"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:"`
-    )
   })
 
   it('should not show a hydration error when using `useId` in a client component', async () => {
@@ -598,14 +674,8 @@ describe('Error overlay for hydration errors in App router', () => {
 
     const { browser } = sandbox
     const logs = await browser.log()
-    const errors = logs
-      .filter((x) => x.source === 'error')
-      .map((x) => x.message)
-      .join('\n')
-
-    expect(errors).not.toInclude(
-      'Warning: Prop `%s` did not match. Server: %s Client: %s'
-    )
+    const errors = logs.filter((x) => x.source === 'error')
+    expect(errors).toEqual([])
   })
 
   it('should only show one hydration error when bad nesting happened - p under p', async () => {
@@ -629,22 +699,16 @@ describe('Error overlay for hydration errors in App router', () => {
       ])
     )
 
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(2)
+      expect(await getToastErrorCount(browser)).toBe(2)
     })
 
-    const description = await session.getRedboxDescription()
-    expect(description).toContain(
-      'In HTML, <p> cannot be a descendant of <p>.\nThis will cause a hydration error.'
-    )
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -659,7 +723,49 @@ describe('Error overlay for hydration errors in App router', () => {
                                <Page params={Promise} searchParams={Promise}>
      >                           <p>
      >                             <p>
-                             ..."
+                             ...",
+         "description": "In HTML, <p> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (6:7) @ Page
+     > 6 |       <p>Nested p tags</p>
+         |       ^",
+         "stack": [
+           "p <anonymous> (0:0)",
+           "Page app/page.js (6:7)",
+         ],
+       },
+       {
+         "componentStack": "...
+         <RenderFromTemplateContext>
+           <ScrollAndFocusHandler segmentPath={[...]}>
+             <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
+               <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
+                 <LoadingBoundary loading={null}>
+                   <HTTPAccessFallbackBoundary notFound={[...]} forbidden={undefined} unauthorized={undefined}>
+                     <HTTPAccessFallbackErrorBoundary pathname="/" notFound={[...]} forbidden={undefined} ...>
+                       <RedirectBoundary>
+                         <RedirectErrorBoundary router={{...}}>
+                           <InnerLayoutRouter url="/" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
+                             <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
+                               <Page params={Promise} searchParams={Promise}>
+     >                           <p>
+     >                             <p>
+                             ...",
+         "description": "In HTML, <p> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Runtime Error",
+         "source": "app/page.js (6:7) @ Page
+     > 6 |       <p>Nested p tags</p>
+         |       ^",
+         "stack": [
+           "p <anonymous> (0:0)",
+           "Page app/page.js (6:7)",
+         ],
+       },
+     ]
     `)
   })
 
@@ -688,22 +794,16 @@ describe('Error overlay for hydration errors in App router', () => {
       ])
     )
 
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(2)
+      expect(await getToastErrorCount(browser)).toBe(2)
     })
 
-    const description = await session.getRedboxDescription()
-    expect(description).toContain(
-      'In HTML, <div> cannot be a descendant of <p>.\nThis will cause a hydration error.'
-    )
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "componentStack": "...
          <ScrollAndFocusHandler segmentPath={[...]}>
            <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
              <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
@@ -719,7 +819,50 @@ describe('Error overlay for hydration errors in App router', () => {
                                  <div>
      >                             <p>
      >                               <div>
-                           ..."
+                           ...",
+         "description": "In HTML, <div> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (8:11) @ Page
+     >  8 |           <div>Nested div under p tag</div>
+          |           ^",
+         "stack": [
+           "div <anonymous> (0:0)",
+           "Page app/page.js (8:11)",
+         ],
+       },
+       {
+         "componentStack": "...
+         <ScrollAndFocusHandler segmentPath={[...]}>
+           <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
+             <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
+               <LoadingBoundary loading={null}>
+                 <HTTPAccessFallbackBoundary notFound={[...]} forbidden={undefined} unauthorized={undefined}>
+                   <HTTPAccessFallbackErrorBoundary pathname="/" notFound={[...]} forbidden={undefined} ...>
+                     <RedirectBoundary>
+                       <RedirectErrorBoundary router={{...}}>
+                         <InnerLayoutRouter url="/" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
+                           <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
+                             <Page params={Promise} searchParams={Promise}>
+                               <div>
+                                 <div>
+     >                             <p>
+     >                               <div>
+                           ...",
+         "description": "In HTML, <div> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Runtime Error",
+         "source": "app/page.js (8:11) @ Page
+     >  8 |           <div>Nested div under p tag</div>
+          |           ^",
+         "stack": [
+           "div <anonymous> (0:0)",
+           "Page app/page.js (8:11)",
+         ],
+       },
+     ]
     `)
   })
 
@@ -739,23 +882,45 @@ describe('Error overlay for hydration errors in App router', () => {
       ])
     )
 
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(2)
+      expect(await getToastErrorCount(browser)).toBe(2)
     })
 
-    const description = await session.getRedboxDescription()
-    expect(description).toMatchInlineSnapshot(`
-     "In HTML, <tr> cannot be a child of <div>.
-     This will cause a hydration error."
-    `)
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "componentStack": "...
+         <RenderFromTemplateContext>
+           <ScrollAndFocusHandler segmentPath={[...]}>
+             <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
+               <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
+                 <LoadingBoundary loading={null}>
+                   <HTTPAccessFallbackBoundary notFound={[...]} forbidden={undefined} unauthorized={undefined}>
+                     <HTTPAccessFallbackErrorBoundary pathname="/" notFound={[...]} forbidden={undefined} ...>
+                       <RedirectBoundary>
+                         <RedirectErrorBoundary router={{...}}>
+                           <InnerLayoutRouter url="/" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
+                             <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
+                               <Page params={Promise} searchParams={Promise}>
+     >                           <div>
+     >                             <tr>
+                             ...",
+         "description": "In HTML, <tr> cannot be a child of <div>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (3:15) @ Page
+     > 3 |   return <div><tr></tr></div>
+         |               ^",
+         "stack": [
+           "tr <anonymous> (0:0)",
+           "Page app/page.js (3:15)",
+         ],
+       },
+       {
+         "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -770,7 +935,20 @@ describe('Error overlay for hydration errors in App router', () => {
                                <Page params={Promise} searchParams={Promise}>
      >                           <div>
      >                             <tr>
-                             ..."
+                             ...",
+         "description": "In HTML, <tr> cannot be a child of <div>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Runtime Error",
+         "source": "app/page.js (3:15) @ Page
+     > 3 |   return <div><tr></tr></div>
+         |               ^",
+         "stack": [
+           "tr <anonymous> (0:0)",
+           "Page app/page.js (3:15)",
+         ],
+       },
+     ]
     `)
   })
 
@@ -793,21 +971,16 @@ describe('Error overlay for hydration errors in App router', () => {
       ])
     )
 
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(3)
+      expect(await getToastErrorCount(browser)).toBe(3)
     })
 
-    const description = await session.getRedboxDescription()
-    expect(description).toContain(
-      'In HTML, <p> cannot be a descendant of <p>.\nThis will cause a hydration error.'
-    )
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-    expect(pseudoHtml).toMatchInlineSnapshot(`
-     "...
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "componentStack": "...
          <RenderFromTemplateContext>
            <ScrollAndFocusHandler segmentPath={[...]}>
              <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
@@ -826,7 +999,66 @@ describe('Error overlay for hydration errors in App router', () => {
                                        <span>
                                          <span>
      >                                     <p>
-                             ..."
+                             ...",
+         "description": "In HTML, <p> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (5:32) @ Page
+     > 5 |     <p><span><span><span><span><p>hello world</p></span></span></span></span></p>
+         |                                ^",
+         "stack": [
+           "p <anonymous> (0:0)",
+           "Page app/page.js (5:32)",
+         ],
+       },
+       {
+         "description": "<p> cannot contain a nested <p>.
+     See this log for the ancestor stack trace.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/page.js (5:5) @ Page
+     > 5 |     <p><span><span><span><span><p>hello world</p></span></span></span></span></p>
+         |     ^",
+         "stack": [
+           "p <anonymous> (0:0)",
+           "Page app/page.js (5:5)",
+         ],
+       },
+       {
+         "componentStack": "...
+         <RenderFromTemplateContext>
+           <ScrollAndFocusHandler segmentPath={[...]}>
+             <InnerScrollAndFocusHandler segmentPath={[...]} focusAndScrollRef={{apply:false, ...}}>
+               <ErrorBoundary errorComponent={undefined} errorStyles={undefined} errorScripts={undefined}>
+                 <LoadingBoundary loading={null}>
+                   <HTTPAccessFallbackBoundary notFound={[...]} forbidden={undefined} unauthorized={undefined}>
+                     <HTTPAccessFallbackErrorBoundary pathname="/" notFound={[...]} forbidden={undefined} ...>
+                       <RedirectBoundary>
+                         <RedirectErrorBoundary router={{...}}>
+                           <InnerLayoutRouter url="/" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
+                             <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
+                               <Page params={Promise} searchParams={Promise}>
+     >                           <p>
+                                   <span>
+                                     <span>
+                                       <span>
+                                         <span>
+     >                                     <p>
+                             ...",
+         "description": "In HTML, <p> cannot be a descendant of <p>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Runtime Error",
+         "source": "app/page.js (5:32) @ Page
+     > 5 |     <p><span><span><span><span><p>hello world</p></span></span></span></span></p>
+         |                                ^",
+         "stack": [
+           "p <anonymous> (0:0)",
+           "Page app/page.js (5:32)",
+         ],
+       },
+     ]
     `)
   })
 
@@ -862,25 +1094,73 @@ describe('Error overlay for hydration errors in App router', () => {
         ],
       ])
     )
-    const { session, browser } = sandbox
-    await session.openRedbox()
+    const { browser } = sandbox
 
     await retry(async () => {
-      expect(await getRedboxTotalErrorCount(browser)).toBe(
+      expect(await getToastErrorCount(browser)).toBe(
         // One error for "Cannot render a sync or defer <script>"
         3
       )
     })
 
-    // TODO: assert on 2nd error being "In HTML, <script> cannot be a child of <html>."
-    // TODO: assert on 3rd error that's specific to owner stacks
-    const description = await session.getRedboxDescription()
-    expect(description).toMatchInlineSnapshot(
-      `"Cannot render a sync or defer <script> outside the main document without knowing its order. Try adding async="" or moving it into the root <head> tag."`
-    )
-
-    const pseudoHtml = await session.getRedboxComponentStack()
-    // 1st error has no component context.
-    expect(pseudoHtml).toMatchInlineSnapshot(`null`)
+    await expect(browser).toDisplayCollapsedRedbox(`
+     [
+       {
+         "description": "Cannot render a sync or defer <script> outside the main document without knowing its order. Try adding async="" or moving it into the root <head> tag.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/layout.js (7:7) @ Layout
+     >  7 |       <Script
+          |       ^",
+         "stack": [
+           "Layout app/layout.js (7:7)",
+         ],
+       },
+       {
+         "componentStack": "...
+         <HotReload assetPrefix="" globalError={[...]}>
+           <AppDevOverlay state={{nextId:1, ...}} globalError={[...]}>
+             <AppDevOverlayErrorBoundary globalError={[...]} onError={function bound dispatchSetState}>
+               <ReplaySsrOnlyErrors>
+               <DevRootHTTPAccessFallbackBoundary>
+                 <HTTPAccessFallbackBoundary notFound={<NotAllowedRootHTTPFallbackError>}>
+                   <HTTPAccessFallbackErrorBoundary pathname="/" notFound={<NotAllowedRootHTTPFallbackError>} ...>
+                     <RedirectBoundary>
+                       <RedirectErrorBoundary router={{...}}>
+                         <Head>${isTurbopack ? '\n                         <script>\n                         <script>' : ''}
+                         <Layout>
+     >                     <html>
+                             <body>
+                             <Script src="https://ex..." strategy="beforeInte...">
+     >                         <script nonce={undefined} dangerouslySetInnerHTML={{__html:"(self.__ne..."}}>
+                         ...
+             ...",
+         "description": "In HTML, <script> cannot be a child of <html>.
+     This will cause a hydration error.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/layout.js (7:7) @ Layout
+     >  7 |       <Script
+          |       ^",
+         "stack": [
+           "script <anonymous> (0:0)",
+           "Layout app/layout.js (7:7)",
+         ],
+       },
+       {
+         "description": "<html> cannot contain a nested <script>.
+     See this log for the ancestor stack trace.",
+         "environmentLabel": null,
+         "label": "Console Error",
+         "source": "app/layout.js (5:5) @ Layout
+     > 5 |     <html>
+         |     ^",
+         "stack": [
+           "html <anonymous> (0:0)",
+           "Layout app/layout.js (5:5)",
+         ],
+       },
+     ]
+    `)
   })
 })