Skip to content

Commit

Permalink
fix: prevent infinite loop on prettyDOM calls (#7250)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsirlucas authored Jan 16, 2025
1 parent 84287fc commit a3a46a5
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 1 deletion.
6 changes: 6 additions & 0 deletions packages/browser/src/node/providers/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
})
}

// unhandled page crashes will hang vitest process
page.on('crash', () => {
const session = this.project.vitest._browserSessions.getSession(sessionId)
session?.reject(new Error('Page crashed when executing tests'))
})

return page
}

Expand Down
3 changes: 2 additions & 1 deletion packages/utils/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ export function stringify(
})
}

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

Expand Down
12 changes: 12 additions & 0 deletions test/browser/fixtures/browser-crash/browser-crash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { commands } from '@vitest/browser/context'
import { it } from 'vitest'

declare module '@vitest/browser/context' {
interface BrowserCommands {
forceCrash: () => Promise<void>
}
}

it('fails gracefully when browser crashes', async () => {
await commands.forceCrash()
})
39 changes: 39 additions & 0 deletions test/browser/fixtures/browser-crash/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
import { instances, provider } from '../../settings'
import { BrowserCommand } from 'vitest/node'

const forceCrash: BrowserCommand<[]> = async (context) => {
const browser = context.context.browser().browserType().name()
if (browser === 'chromium') {
await context.page.goto('chrome://crash')
}

if (browser === 'firefox') {
await context.page.goto('about:crashcontent')
}

throw new Error(`Browser crash not supported for ${browser}`)
}

export default defineConfig({
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
test: {
browser: {
commands: { forceCrash },
enabled: true,
provider,
instances: instances.map(instance => ({
...instance,
context: {
actionTimeout: 500,
},
})),
},
expect: {
poll: {
timeout: 500,
},
},
},
})
17 changes: 17 additions & 0 deletions test/browser/specs/browser-crash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect, test } from 'vitest'
import { instances, provider, runBrowserTests } from './utils'

// TODO handle webdriverio. Currently they
// expose no trustable way to detect browser crashes.
test.runIf(provider === 'playwright')('fails gracefully when browser crashes', async () => {
const { stderr } = await runBrowserTests({
root: './fixtures/browser-crash',
reporters: [['verbose', { isTTY: false }]],
browser: {
// webkit has no support for simulating browser crash
instances: instances.filter(item => item.name !== 'webkit'),
},
})

expect(stderr).contains('Page crashed when executing tests')
})
10 changes: 10 additions & 0 deletions test/browser/test/__snapshots__/utils.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ exports[`prints the element with attributes 1`] = `
</div>
</body>"
`;

exports[`should handle DOM content bigger than maxLength 1`] = `
"<body>
<div>
<div>
<div />
</div>
</div>
</body>..."
`;
15 changes: 15 additions & 0 deletions test/browser/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ test('prints the element with attributes', async () => {

expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
})

test('should handle DOM content bigger than maxLength', async () => {
const depth = 200
const maxContent = 150

const openingTags = '<div>'.repeat(depth)
const closingTags = '</div>'.repeat(depth)
const domString = `${openingTags}${closingTags}`

const parentDiv = document.createElement('div')
parentDiv.innerHTML = domString

document.body.appendChild(parentDiv)
expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot()
})

0 comments on commit a3a46a5

Please sign in to comment.