Skip to content

Commit a3a46a5

Browse files
authoredJan 16, 2025··
fix: prevent infinite loop on prettyDOM calls (#7250)
1 parent 84287fc commit a3a46a5

File tree

7 files changed

+101
-1
lines changed

7 files changed

+101
-1
lines changed
 

‎packages/browser/src/node/providers/playwright.ts

+6
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
185185
})
186186
}
187187

188+
// unhandled page crashes will hang vitest process
189+
page.on('crash', () => {
190+
const session = this.project.vitest._browserSessions.getSession(sessionId)
191+
session?.reject(new Error('Page crashed when executing tests'))
192+
})
193+
188194
return page
189195
}
190196

‎packages/utils/src/display.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ export function stringify(
7373
})
7474
}
7575

76+
// Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249
7677
return result.length >= MAX_LENGTH && maxDepth > 1
77-
? stringify(object, Math.floor(maxDepth / 2))
78+
? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), { maxLength, ...options })
7879
: result
7980
}
8081

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { commands } from '@vitest/browser/context'
2+
import { it } from 'vitest'
3+
4+
declare module '@vitest/browser/context' {
5+
interface BrowserCommands {
6+
forceCrash: () => Promise<void>
7+
}
8+
}
9+
10+
it('fails gracefully when browser crashes', async () => {
11+
await commands.forceCrash()
12+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { defineConfig } from 'vitest/config'
3+
import { instances, provider } from '../../settings'
4+
import { BrowserCommand } from 'vitest/node'
5+
6+
const forceCrash: BrowserCommand<[]> = async (context) => {
7+
const browser = context.context.browser().browserType().name()
8+
if (browser === 'chromium') {
9+
await context.page.goto('chrome://crash')
10+
}
11+
12+
if (browser === 'firefox') {
13+
await context.page.goto('about:crashcontent')
14+
}
15+
16+
throw new Error(`Browser crash not supported for ${browser}`)
17+
}
18+
19+
export default defineConfig({
20+
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
21+
test: {
22+
browser: {
23+
commands: { forceCrash },
24+
enabled: true,
25+
provider,
26+
instances: instances.map(instance => ({
27+
...instance,
28+
context: {
29+
actionTimeout: 500,
30+
},
31+
})),
32+
},
33+
expect: {
34+
poll: {
35+
timeout: 500,
36+
},
37+
},
38+
},
39+
})
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { expect, test } from 'vitest'
2+
import { instances, provider, runBrowserTests } from './utils'
3+
4+
// TODO handle webdriverio. Currently they
5+
// expose no trustable way to detect browser crashes.
6+
test.runIf(provider === 'playwright')('fails gracefully when browser crashes', async () => {
7+
const { stderr } = await runBrowserTests({
8+
root: './fixtures/browser-crash',
9+
reporters: [['verbose', { isTTY: false }]],
10+
browser: {
11+
// webkit has no support for simulating browser crash
12+
instances: instances.filter(item => item.name !== 'webkit'),
13+
},
14+
})
15+
16+
expect(stderr).contains('Page crashed when executing tests')
17+
})

‎test/browser/test/__snapshots__/utils.test.ts.snap

+10
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,13 @@ exports[`prints the element with attributes 1`] = `
3535
</div>
3636
</body>"
3737
`;
38+
39+
exports[`should handle DOM content bigger than maxLength 1`] = `
40+
"<body>
41+
<div>
42+
<div>
43+
<div />
44+
</div>
45+
</div>
46+
</body>..."
47+
`;

‎test/browser/test/utils.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,18 @@ test('prints the element with attributes', async () => {
3737

3838
expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
3939
})
40+
41+
test('should handle DOM content bigger than maxLength', async () => {
42+
const depth = 200
43+
const maxContent = 150
44+
45+
const openingTags = '<div>'.repeat(depth)
46+
const closingTags = '</div>'.repeat(depth)
47+
const domString = `${openingTags}${closingTags}`
48+
49+
const parentDiv = document.createElement('div')
50+
parentDiv.innerHTML = domString
51+
52+
document.body.appendChild(parentDiv)
53+
expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot()
54+
})

0 commit comments

Comments
 (0)
Please sign in to comment.