Files
2026-07-14 17:34:20 +09:00

280 lines
8.7 KiB
TypeScript

import path from 'pathe'
import { expect, test } from 'vitest'
import { buildTestProjectTree } from '../../test-utils'
import { instances, provider, runBrowserTests, runInlineBrowserTests } from './utils'
test('prints correct unhandled error stack', async () => {
const { stderr } = await runBrowserTests({
root: './fixtures/unhandled',
})
expect(stderr).toContain('throw-unhandled-error.test.ts:9:11')
expect(stderr).toContain('This error originated in "throw-unhandled-error.test.ts" test file.')
expect(stderr).toContain('The last test to run before this error was "unhandled exception".')
if (instances.some(({ browser }) => browser === 'webkit')) {
expect(stderr).toContain('throw-unhandled-error.test.ts:9:15')
}
})
test('ignores unhandled errors', async () => {
const { stderr } = await runBrowserTests({
root: './fixtures/unhandled',
onUnhandledError(error) {
if (error.message.includes('custom_unhandled_error')) {
return false
}
},
})
expect(stderr).toBe('')
})
test('disables tracking', async () => {
const { stderr } = await runBrowserTests({
root: './fixtures/unhandled',
browser: {
trackUnhandledErrors: false,
},
})
expect(stderr).toBe('')
})
test('print unhandled non error', async () => {
const { testTree, stderr } = await runBrowserTests({
root: './fixtures/unhandled-non-error',
})
expect(stderr).toContain('[Error: ResizeObserver loop completed with undelivered notifications.]')
expect(testTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"ResizeObserver error": "passed",
},
}
`)
})
test('throws an error if test reloads the iframe during a test run', async () => {
const { stderr, fs } = await runInlineBrowserTests({
'iframe-reload.test.ts': `
import { test } from 'vitest';
test('reload iframe', () => {
location.reload();
});
`,
})
expect(stderr).toContain(
`The iframe for "${fs.resolveFile('./iframe-reload.test.ts')}" was reloaded during a test.`,
)
})
test('cannot use fs commands if write is disabled', async () => {
const { stderr, fs } = await runInlineBrowserTests({
'fs-commands.test.ts': `
import { test, expect, recordArtifact } from 'vitest'
import { commands } from 'vitest/browser'
test.describe('fs security', () => {
test('fs writeFile throws an error', async () => {
await commands.writeFile('/test-file.txt', 'Hello World')
})
test('fs removeFile throws an error', async () => {
await commands.removeFile('/test-file.txt')
})
test('doesnt write attachment to disk', async ({ annotate }) => {
await annotate('test-attachment', { data: 'Test Attachment', path: '/test-attachment.txt' })
})
test('cannot record attachments inside artifact', async ({ task }) => {
await recordArtifact(task, {
attachments: [{ data: 'Artifact Attachment', path: '/artifact-attachment.txt' }],
type: 'my-custom',
})
})
test('snapshot saves are not saved', () => {
expect('snapshot content').toMatchSnapshot()
})
})
`,
'./__snapshots__/basic.test.js.snap': `// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html`,
'basic.test.js': `
import { test } from 'vitest'
test('basic test', () => {
expect(1 + 1).toBe(2)
})
`,
}, {
api: {
allowExec: false,
allowWrite: false,
},
$cliOptions: {
update: true,
},
})
const errors = stderr.split('\n').filter(line => line.includes('Cannot modify file "/test-file.txt".'))
expect(errors).toHaveLength(2 * instances.length)
expect(stderr).toContain(
`Cannot save snapshot file "${fs.resolveFile('./__snapshots__/fs-commands.test.ts.snap')}". File writing is disabled because server is exposed to the internet`,
)
expect(stderr).toContain(
`Cannot remove snapshot file "${fs.resolveFile('./__snapshots__/basic.test.js.snap')}". File writing is disabled because server is exposed to the internet`,
)
// we don't throw an error if cannot write attachment, just warn
expect(stderr).toContain(
'Cannot record annotation attachment because file writing is disabled',
)
expect(stderr).toContain(
'Cannot record attachments ("/artifact-attachment.txt") because file writing is disabled, removing attachments from artifact "my-custom".',
)
})
test('prints source-mapped stack for optimized dependency', async () => {
const { results, ctx } = await runBrowserTests({
root: './fixtures/error-in-dep',
})
const projectTree = buildTestProjectTree(results, (testCase) => {
const result = testCase.result()
return result.errors?.map((e) => {
const stacks = e.stacks?.map((s) => {
const normalizedFile = path
.relative(ctx.config.root, s.file)
.replace(
/node_modules[\\/]\.pnpm[\\/][^\\/\n]+[\\/]node_modules[\\/]/g,
'node_modules/.pnpm/<normalized>/node_modules/',
)
return `${s.method} at ${normalizedFile}:${s.line}:${s.column}`
})
return ({ message: e.message, stacks })
})
})
expect(Object.keys(projectTree).sort()).toEqual(instances.map(i => i.browser).sort())
for (const [name, tree] of Object.entries(projectTree)) {
if (name === 'webkit') {
expect(tree).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"fail": [
{
"message": "this is test dependency error",
"stacks": [
"throwDepError at ../../../../node_modules/.pnpm/<normalized>/node_modules/test-dep-error/index.js:2:13",
" at basic.test.ts:5:3",
],
},
],
},
}
`)
}
else {
expect(tree).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"fail": [
{
"message": "this is test dependency error",
"stacks": [
"throwDepError at ../../../../node_modules/.pnpm/<normalized>/node_modules/test-dep-error/index.js:2:9",
" at basic.test.ts:5:3",
],
},
],
},
}
`)
}
}
})
test.runIf(provider.name === 'playwright')('cannot use cdp if write or exec is disabled', async () => {
const result = await runInlineBrowserTests({
'cdp.test.ts': `
import { expect, test } from 'vitest'
import { cdp, server } from 'vitest/browser'
test('cdp throws an error', async () => {
await cdp().send('Runtime.evaluate', { expression: '1 + 1' })
})
`,
}, {
api: {
allowExec: false,
allowWrite: false,
},
browser: {
instances: [{ browser: 'chromium' }],
screenshotFailures: false,
},
})
expect(result.errorTree({ project: true })).toMatchInlineSnapshot(`
{
"chromium": {
"cdp.test.ts": {
"cdp throws an error": [
"Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/api.",
],
},
},
}
`)
})
test('upload is blocked for files denied by server.fs.deny', async () => {
const result = await runBrowserTests({
root: './fixtures/command-permissions-upload-denied',
project: [instances[0].browser],
})
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"upload-denied.test.ts": {
"upload denied path": [
"Access denied to "<root>/my-secret.txt". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.",
],
},
}
`)
})
test('takeScreenshot is blocked for files denied by server.fs.deny', async () => {
const result = await runBrowserTests({
root: './fixtures/command-permissions-screenshot-denied',
project: [instances[0].browser],
})
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"screenshot-denied.test.ts": {
"screenshot denied path": [
"Access denied to "<root>/my-secret.png". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.",
],
},
}
`)
})
test('takeScreenshot is blocked when write is disabled', async () => {
const result = await runBrowserTests({
root: './fixtures/command-permissions-screenshot-no-write',
project: [instances[0].browser],
})
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"screenshot-write.test.ts": {
"screenshot blocked": [
"Cannot modify file "<root>/out.png". File writing is disabled because the server is exposed to the internet, see https://vitest.dev/config/browser/api.",
],
},
}
`)
})