[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

test: rework `test/ui` (#10237)

Co-authored-by: Codex <noreply@openai.com>

authored by

Hiroshi Ogawa
Codex
and committed by
GitHub
(May 13, 2026, 9:06 AM +0200) 964b67f1 e8e4a1b4

+1230 -1316
+1
eslint.config.js
··· 14 14 '**/assets/**', 15 15 '**/*.d.ts', 16 16 '**/*.timestamp-*', 17 + '**/test-results', 17 18 'test/unit/src/self', 18 19 'test/unit/test/mocking/already-hoisted.test.ts', 19 20 'test/cache/cache/.vitest-base/results.json',
+4 -3
test/ui/README.md
··· 1 1 # test/ui 2 2 3 3 ```sh 4 - # run e2e 4 + # run e2e on playwright 5 5 pnpm test 6 + pnpm test --ui 6 7 7 8 # run fixture projects 8 - pnpm test-fixtures --ui 9 - pnpm test-fixtures --root fixtures-trace 9 + pnpm test-fixtures --root fixtures/main --ui 10 + pnpm test-fixtures --root fixtures/trace 10 11 ```
+1 -3
test/ui/package.json
··· 3 3 "type": "module", 4 4 "private": true, 5 5 "scripts": { 6 - "test": "GITHUB_ACTIONS=false playwright test", 7 - "test-e2e": "GITHUB_ACTIONS=false playwright test", 8 - "test-e2e-ui": "GITHUB_ACTIONS=false playwright test --ui", 6 + "test": "playwright test", 9 7 "test-fixtures": "vitest" 10 8 }, 11 9 "devDependencies": {
-42
test/ui/vitest.config.ts
··· 1 - import { resolve } from 'node:path' 2 - import { playwright } from '@vitest/browser-playwright' 3 - import { defaultExclude, defineConfig } from 'vitest/config' 4 - 5 - export default defineConfig({ 6 - test: { 7 - exclude: ['**/fixtures-trace/**', '**/fixtures-trace-stream/**', ...defaultExclude], 8 - coverage: { 9 - reportOnFailure: true, 10 - }, 11 - tags: [ 12 - { name: 'db' }, 13 - { name: 'flaky' }, 14 - ], 15 - projects: [{ 16 - extends: true, 17 - test: { 18 - name: 'fixtures', 19 - dir: './fixtures', 20 - environment: 'happy-dom', 21 - }, 22 - }, { 23 - extends: true, 24 - test: { 25 - name: 'browser', 26 - dir: './fixtures-browser', 27 - browser: { 28 - enabled: true, 29 - headless: true, 30 - provider: playwright(), 31 - instances: [{ browser: 'chromium' }], 32 - screenshotFailures: false, 33 - expect: { 34 - toMatchScreenshot: { 35 - resolveScreenshotPath: ({ root, testFileDirectory, arg, ext }) => resolve(root, testFileDirectory, `${arg}${ext}`), 36 - }, 37 - }, 38 - }, 39 - }, 40 - }], 41 - }, 42 - })
-16
test/ui/fixtures-browser/visual-regression.test.ts
··· 1 - import { test } from 'vitest' 2 - import { server } from 'vitest/browser' 3 - 4 - test('visual regression test', async ({ expect, onTestFinished }) => { 5 - const screenshotName = 'visual-regression-screenshot.png' 6 - 7 - onTestFinished(async () => { 8 - if (server.config.snapshotOptions.updateSnapshot !== 'none') { 9 - await server.commands.removeFile(`fixtures-browser/${screenshotName}`) 10 - } 11 - }) 12 - 13 - await expect(expect(document.body).toMatchScreenshot(screenshotName)).rejects.toThrow( 14 - 'No existing reference screenshot found', 15 - ) 16 - })
-18
test/ui/fixtures-trace-stream/basic.test.ts
··· 1 - import { test } from 'vitest' 2 - import { page } from 'vitest/browser' 3 - import { waitForGate } from './helper' 4 - 5 - test('simple', async () => { 6 - document.body.innerHTML = '<button>A</button>' 7 - await page.getByRole('button', { name: 'A' }).mark('render-a') 8 - 9 - await waitForGate('b') 10 - 11 - document.body.innerHTML += '<button>B</button>' 12 - await page.getByRole('button', { name: 'B' }).mark('render-b') 13 - 14 - await waitForGate('c') 15 - 16 - document.body.innerHTML += '<button>C</button>' 17 - await page.getByRole('button', { name: 'C' }).mark('render-c') 18 - })
-21
test/ui/fixtures-trace-stream/helper.ts
··· 1 - import { vi } from 'vitest' 2 - import { commands } from 'vitest/browser' 3 - 4 - // use file system command to communicate with e2e 5 - // to precisely control the timing of incremental trace update 6 - export async function waitForGate(name: string) { 7 - if (!(import.meta as any).env.TEST_GATE_FILE) { 8 - await new Promise(r => setTimeout(r, 2000)) 9 - return 10 - } 11 - const gatePath = `./node_modules/.vitest-e2e/${name}.txt` 12 - await vi.waitUntil(async () => { 13 - try { 14 - const content = await commands.readFile(gatePath) 15 - return content.includes('open') 16 - } 17 - catch { 18 - return false 19 - } 20 - }, { timeout: 10000 }) 21 - }
-29
test/ui/fixtures-trace-stream/range.test.ts
··· 1 - import { expect, test } from 'vitest' 2 - import { page } from 'vitest/browser' 3 - import { waitForGate } from './helper' 4 - 5 - test('expect', async () => { 6 - document.body.innerHTML = '<button>A</button>' 7 - await Promise.all([ 8 - (async () => { 9 - await expect 10 - .element(page.getByRole('button', { name: 'B' }), { timeout: 10000 }) 11 - .toBeVisible() 12 - })(), 13 - (async () => { 14 - await waitForGate('expect-b') 15 - document.body.innerHTML = '<button>B</button>' 16 - })(), 17 - ]) 18 - await Promise.all([ 19 - (async () => { 20 - await expect 21 - .element(page.getByRole('button').filter({ hasText: 'C' }), { timeout: 10000 }) 22 - .toHaveAttribute('data-testid', 'c') 23 - })(), 24 - (async () => { 25 - await waitForGate('expect-c') 26 - document.body.innerHTML = '<button data-testid="c">C</button>' 27 - })(), 28 - ]) 29 - })
-21
test/ui/fixtures-trace-stream/vitest.config.ts
··· 1 - import { playwright } from '@vitest/browser-playwright' 2 - import { defineConfig } from 'vitest/config' 3 - 4 - export default defineConfig({ 5 - test: { 6 - ui: true, 7 - browser: { 8 - enabled: true, 9 - provider: playwright(), 10 - instances: [ 11 - { browser: 'chromium' }, 12 - ], 13 - headless: true, 14 - ui: false, 15 - traceView: { 16 - enabled: true, 17 - }, 18 - screenshotFailures: false, 19 - }, 20 - }, 21 - })
-84
test/ui/fixtures-trace/basic.test.ts
··· 1 - import { expect, onTestFinished, test } from 'vitest' 2 - import { commands, page } from 'vitest/browser' 3 - 4 - // tests for full snapshot/replay integration. 5 - // partly extracted from artifact metadata tests in 6 - // test/browser/fixtures/trace/*.test.ts 7 - 8 - test('simple', async () => { 9 - document.body.innerHTML = '<button>Simple</button>' 10 - await page.getByRole('button').mark('Render simple') 11 - }) 12 - 13 - test('switch-target', async () => { 14 - document.body.innerHTML = '<button>Switch Target</button>' 15 - await page.getByRole('button').mark('Render switch target') 16 - }) 17 - 18 - test('pseudo-state', async () => { 19 - document.body.innerHTML = ` 20 - <style> 21 - .test-target { 22 - background: rgb(255, 200, 200); 23 - } 24 - .test-hover:hover, 25 - .test-focus:focus, 26 - .test-focus-within:focus-within, 27 - .test-active:active, 28 - .test-focus-visible:focus-visible, 29 - .test-none 30 - { 31 - background: rgb(253, 224, 71); 32 - } 33 - </style> 34 - <button class="test-target test-hover">Test hover 1</button> 35 - <hr /> 36 - <button class="test-target test-hover">Test hover 2</button> 37 - <hr /> 38 - <label style="display: block; padding: 8px;"> 39 - Test focus 40 - <input class="test-target test-focus" placeholder="focus-placeholder"> 41 - </label> 42 - <hr /> 43 - <label class="test-target test-focus-within" style="display: block; padding: 8px;"> 44 - Test focus-within 45 - <input placeholder="focus-within-placeholder"> 46 - </label> 47 - <hr /> 48 - <button class="test-target test-active">Test active</button> 49 - <hr /> 50 - <label style="display: block; padding: 8px;"> 51 - Test focus-visible 52 - <input class="test-target test-focus-visible" placeholder="focus-visible-placeholder"> 53 - </label> 54 - ` 55 - await page.getByRole('button', { name: 'Test hover 1' }).hover() 56 - await page.getByRole('button', { name: 'Test hover 2' }).click() 57 - await page.getByPlaceholder('focus-placeholder').click() 58 - await page.getByPlaceholder('focus-placeholder').fill('focus-done') 59 - await page.getByPlaceholder('focus-within-placeholder').fill('focus-within-done') 60 - await (commands as any).mousedown('.test-active') 61 - await page.getByRole('button', { name: 'Test active' }).mark('Test active') 62 - await page.getByPlaceholder('focus-visible-placeholder').fill('focus-visible-done') 63 - }) 64 - 65 - test('css-link', async () => { 66 - const link = document.createElement('link') 67 - link.rel = 'stylesheet' 68 - link.href = '/assets/trace-style.css' 69 - document.head.append(link) 70 - onTestFinished(() => { 71 - link.remove() 72 - }) 73 - document.body.innerHTML = '<button class="trace-linked-css">Linked CSS</button>' 74 - await expect.element(page.getByRole('button', { name: 'Linked CSS' })) 75 - .toHaveStyle({ color: 'rgb(50, 100, 255)' }) 76 - }) 77 - 78 - test('image', async () => { 79 - document.body.innerHTML = '<img src="/assets/trace-pixel.svg" alt="local trace asset" width="24" height="24">' 80 - await expect.element(page.getByAltText('local trace asset')) 81 - .not 82 - .toHaveProperty('naturalWidth', 0) 83 - await page.getByAltText('local trace asset').mark('Render image') 84 - })
-40
test/ui/fixtures-trace/retry.test.ts
··· 1 - import type { TestContext } from 'vitest' 2 - import { test, vi } from 'vitest' 3 - import { page } from 'vitest/browser' 4 - 5 - const renderContext = vi.defineHelper(async (context: TestContext) => { 6 - const result = context.task.result 7 - const content = ` 8 - <ul> 9 - <li>retryCount: ${result?.retryCount ?? '(none)'}</li> 10 - <li>repeatCount: ${result?.repeatCount ?? '(none)'}</li> 11 - </ul> 12 - ` 13 - document.body.innerHTML = content 14 - await page.getByRole('list').mark(`renderHelper`) 15 - }) 16 - 17 - test('repeated test', { repeats: 2 }, async ({ task }) => { 18 - await renderContext(task.context) 19 - }) 20 - 21 - test('retried test', { retry: 2 }, async ({ task }) => { 22 - await renderContext(task.context) 23 - if (task.result?.retryCount !== 2) { 24 - throw new Error(`failed test at retry count ${task.result?.retryCount}`) 25 - } 26 - }) 27 - 28 - test('repeated retried tests', { repeats: 2, retry: 2 }, async ({ task }) => { 29 - await renderContext(task.context) 30 - if (task.result?.retryCount !== 2) { 31 - throw new Error(`failed test at retry count ${task.result?.retryCount}`) 32 - } 33 - }) 34 - 35 - test('repeated test retried on later repeat', { repeats: 2, retry: 2 }, async ({ task }) => { 36 - await renderContext(task.context) 37 - if (task.result?.repeatCount === 1 && task.result.retryCount !== 1) { 38 - throw new Error(`failed test at retry count ${task.result?.retryCount}`) 39 - } 40 - })
-34
test/ui/fixtures-trace/scroll.test.ts
··· 1 - import { test } from 'vitest' 2 - import { page } from 'vitest/browser' 3 - 4 - test('scroll', async () => { 5 - const [width, height] = [300, 300] 6 - await page.viewport(width, height) 7 - document.body.innerHTML = ` 8 - <style> 9 - html, 10 - body { 11 - margin: 0; 12 - height: 100vh; 13 - width: 100vw; 14 - background: linear-gradient(-45deg, purple, yellow); 15 - } 16 - .marker { 17 - position: absolute; 18 - width: 100px; 19 - background: white; 20 - border: 1px solid black; 21 - text-align: center; 22 - } 23 - </style> 24 - <main style="height: 600px; width: 600px;"> 25 - <div class="marker" style="top: 0; left: 0;">(0, 0)</div> 26 - <div class="marker" style="top: 0; left: 300px;">(300, 0)</div> 27 - <div class="marker" style="top: 300px; left: 0;">(0, 300)</div> 28 - <div class="marker" style="top: 300px; left: 300px;">(300, 300)</div> 29 - </main> 30 - ` 31 - // scroll to make (300, 300) visible and (0, 0) not visible 32 - window.scrollTo(250, 200) 33 - await page.getByRole('button').mark('Render scroll') 34 - })
-42
test/ui/fixtures-trace/viewport.test.ts
··· 1 - import { test } from 'vitest' 2 - import { page } from 'vitest/browser' 3 - 4 - test('viewport', async () => { 5 - const [width, height] = [500, 300] 6 - await page.viewport(width, height) 7 - document.body.innerHTML = ` 8 - <style> 9 - html, 10 - body { 11 - margin: 0; 12 - height: 100vh; 13 - background: linear-gradient(-45deg, blue, orange); 14 - } 15 - 16 - .viewport-pass { 17 - display: none; 18 - } 19 - 20 - .viewport-fail { 21 - display: block; 22 - } 23 - 24 - @media (width: ${width}px) and (height: ${height}px) { 25 - .viewport-pass { 26 - display: block; 27 - } 28 - 29 - .viewport-fail { 30 - display: none; 31 - } 32 - } 33 - </style> 34 - <div class="viewport-pass"> 35 - PASS: Viewport is ${width}x${height} 36 - </div> 37 - <div class="viewport-fail"> 38 - FAIL: Viewport is not ${width}x${height} 39 - </div> 40 - ` 41 - await page.mark('Render viewport') 42 - })
-34
test/ui/fixtures-trace/vitest.config.ts
··· 1 - import type { BrowserCommand } from 'vitest/node' 2 - import { playwright } from '@vitest/browser-playwright' 3 - import { defineConfig } from 'vitest/config' 4 - 5 - // mousedown through custom command 6 - // https://github.com/vitest-dev/vitest/issues/8190 7 - const mousedownCommand: BrowserCommand<[selector: string]> = async (ctx, selector) => { 8 - await ctx.iframe.locator(selector).hover() 9 - await ctx.page.mouse.down() 10 - } 11 - 12 - export default defineConfig({ 13 - test: { 14 - ui: true, 15 - browser: { 16 - enabled: true, 17 - provider: playwright(), 18 - instances: [ 19 - { browser: 'chromium' }, 20 - ], 21 - headless: true, 22 - ui: false, 23 - traceView: { 24 - enabled: true, 25 - // enabled only on html reporter e2e 26 - // inlineImages: true, 27 - }, 28 - screenshotFailures: false, 29 - commands: { 30 - mousedown: mousedownCommand, 31 - }, 32 - }, 33 - }, 34 - })
-37
test/ui/fixtures/annotated.test.ts
··· 1 - import { test } from 'vitest' 2 - 3 - test('annotated test', async ({ annotate }) => { 4 - await annotate('hello world') 5 - await annotate('second annotation') 6 - }) 7 - 8 - test('annotated typed test', async ({ annotate }) => { 9 - await annotate('beware!', 'warning') 10 - }) 11 - 12 - test('annotated file test', async ({ annotate }) => { 13 - await annotate('file annotation', { 14 - path: './fixtures/example.txt' 15 - }) 16 - }) 17 - 18 - test('annotated image test', async ({ annotate }) => { 19 - await annotate('image annotation', { 20 - path: './fixtures/cute-puppy.jpg' 21 - }) 22 - }) 23 - 24 - test('annotated with body base64', async ({ annotate }) => { 25 - await annotate('body base64 annotation', { 26 - contentType: 'text/markdown', 27 - body: btoa('Hello base64 **markdown**'), 28 - }) 29 - }) 30 - 31 - test('annotated with body utf-8', async ({ annotate }) => { 32 - await annotate('body utf-8 annotation', { 33 - contentType: 'text/markdown', 34 - body: 'Hello utf-8 **markdown**', 35 - bodyEncoding: 'utf-8', 36 - }) 37 - })
-72
test/ui/fixtures/console.test.ts
··· 1 - /* eslint-disable no-console */ 2 - 3 - import { afterAll, beforeAll, it, describe, expect } from "vitest"; 4 - import { prettyDOM } from "@testing-library/dom"; 5 - 6 - // https://github.com/vitest-dev/vitest/issues/2765 7 - it('regexp', () => { 8 - console.log(/(?<char>\w)/) 9 - }) 10 - 11 - // https://github.com/vitest-dev/vitest/issues/3934 12 - it('html-raw', async () => { 13 - console.log(` 14 - <form> 15 - <label for="email">Email Address</label> 16 - <input name="email" /> 17 - <button>Submit</button> 18 - </form> 19 - `); 20 - }) 21 - 22 - // https://github.com/vitest-dev/vitest/issues/1279 23 - it('html-pretty', () => { 24 - const div = document.createElement("div"); 25 - div.innerHTML = ` 26 - <form> 27 - <label for="email">Email Address</label> 28 - <input name="email" /> 29 - <button>Submit</button> 30 - </form> 31 - `.replaceAll(/\n */gm, ""); // strip new lines 32 - console.log(prettyDOM(div)) 33 - }) 34 - 35 - 36 - beforeAll(() => { 37 - console.log('beforeAll') 38 - console.error('beforeAll') 39 - }) 40 - 41 - afterAll(() => { 42 - console.log('afterAll') 43 - console.error('afterAll') 44 - }) 45 - 46 - describe('suite', () => { 47 - beforeAll(() => { 48 - console.log('beforeAll') 49 - console.error('beforeAll') 50 - }) 51 - 52 - afterAll(() => { 53 - console.log('afterAll') 54 - console.error('afterAll') 55 - }) 56 - 57 - describe('nested suite', () => { 58 - beforeAll(() => { 59 - console.log('beforeAll') 60 - console.error('beforeAll') 61 - }) 62 - 63 - afterAll(() => { 64 - console.log('afterAll') 65 - console.error('afterAll') 66 - }) 67 - 68 - it('test', () => { 69 - expect(true).toBe(true) 70 - }) 71 - }) 72 - })
-6
test/ui/fixtures/coverage.test.ts
··· 1 - import { expect, it } from 'vitest' 2 - import { multiply } from './coverage' 3 - 4 - it(multiply, () => { 5 - expect(multiply(2, 3)).toEqual(6) 6 - })
-3
test/ui/fixtures/coverage.ts
··· 1 - export function multiply(n: number, m: number) { 2 - return n * m; 3 - }
test/ui/fixtures/cute-puppy.jpg

This is a binary file and will not be displayed.

-14
test/ui/fixtures/error.test.ts
··· 1 - import { expect, it } from "vitest" 2 - 3 - // https://github.com/vitest-dev/vitest/issues/5321 4 - it('escape html in error diff', () => { 5 - expect('<style>* {border: 2px solid green};</style>').toBe("") 6 - }) 7 - 8 - it('colored error message', () => { 9 - const blue = '\x1B[34m' 10 - const reset = '\x1B[0m' 11 - const message = `${blue}this-is-blue${reset}` 12 - const error = new Error(message) 13 - throw error 14 - })
-1
test/ui/fixtures/example.txt
··· 1 - hello world
-17
test/ui/fixtures/sample.test.ts
··· 1 - import { expect, it } from 'vitest' 2 - 3 - it('add', () => { 4 - // eslint-disable-next-line no-console 5 - console.log('log test') 6 - setTimeout(() => { 7 - throw new Error('error') 8 - }) 9 - setTimeout(() => { 10 - throw 1 11 - }) 12 - expect(1 + 1).toEqual(2) 13 - }) 14 - 15 - it('has tags', { tags: ['db', 'flaky'] }, () => { 16 - // ... 17 - })
-5
test/ui/fixtures/snapshot.test.ts
··· 1 - import { expect, test } from 'vitest'; 2 - 3 - test('wrong snapshot', () => { 4 - expect(1).toMatchInlineSnapshot(`2`) 5 - })
-13
test/ui/fixtures/task-name.test.ts
··· 1 - import { it, expect} from "vitest" 2 - 3 - it('<MyComponent />', () => { 4 - expect(true).toBe(true) 5 - }) 6 - 7 - it('<>\'"', () => { 8 - expect(true).toBe(true) 9 - }) 10 - 11 - it('char () - Square root of nine (9)', () => { 12 - expect(Math.sqrt(9)).toBe(3); 13 - });
+95
test/ui/test/helper.ts
··· 1 + import type { Page } from '@playwright/test' 2 + import type { InlineConfig, PreviewServer } from 'vite' 3 + import type { CliOptions, Vitest } from 'vitest/node' 4 + import assert from 'node:assert' 5 + import { readFileSync } from 'node:fs' 6 + import { Writable } from 'node:stream' 7 + import { expect } from '@playwright/test' 8 + import { preview } from 'vite' 9 + import { startVitest } from 'vitest/node' 10 + 11 + export async function startVitestUi( 12 + cliOptions: CliOptions, 13 + viteOverrides: InlineConfig = {}, 14 + ): Promise<{ vitest: Vitest; url: string }> { 15 + // silence Vitest logs 16 + const stdout = new Writable({ write: (_, __, callback) => callback() }) 17 + const stderr = new Writable({ write: (_, __, callback) => callback() }) 18 + const vitest = await startVitest('test', undefined, cliOptions, viteOverrides, { stdout, stderr }) 19 + 20 + const address = vitest.vite.httpServer?.address() 21 + assert(address && typeof address === 'object', 'Invalid server address') 22 + 23 + return { 24 + vitest, 25 + url: `http://localhost:${address.port}`, 26 + } 27 + } 28 + 29 + export async function startHtmlReportPreview( 30 + cliOptions: CliOptions, 31 + previewOptions: InlineConfig, 32 + ): Promise<{ previewServer: PreviewServer; url: string }> { 33 + const stdout = new Writable({ write: (_, __, callback) => callback() }) 34 + const stderr = new Writable({ write: (_, __, callback) => callback() }) 35 + await startVitest('test', undefined, cliOptions, {}, { stdout, stderr }) 36 + 37 + const previewServer = await preview(previewOptions) 38 + const address = previewServer.httpServer?.address() 39 + assert(address && typeof address === 'object', 'Invalid server address') 40 + 41 + return { 42 + previewServer, 43 + url: `http://localhost:${address.port}`, 44 + } 45 + } 46 + 47 + export async function assertTestCounts(page: Page, { pass, fail }: { pass: number; fail: number }) { 48 + await expect 49 + .soft(page.getByTestId('tests-entry')) 50 + .toContainText( 51 + `${pass} Pass ${fail} Fail ${pass + fail} Total`, 52 + ) 53 + } 54 + 55 + export function getExplorerItem(page: Page, name: string) { 56 + return page.getByTestId('explorer-item').and(page.getByLabel(name, { exact: true })) 57 + } 58 + 59 + export async function openExplorerItem(page: Page, name: string) { 60 + await getExplorerItem(page, name).click() 61 + } 62 + 63 + export async function openExplorerFileItem(page: Page, name: string) { 64 + const item = getExplorerItem(page, name) 65 + await item.hover() 66 + await item.getByTestId('btn-open-details').click() 67 + } 68 + 69 + export async function assertDownloadAttachment( 70 + page: Page, 71 + options: { 72 + name: string 73 + suggestedFilename: string 74 + content: string 75 + }, 76 + ) { 77 + const annotation = page.getByRole('note').filter({ hasText: options.name }) 78 + const downloadPromise = page.waitForEvent('download') 79 + await annotation.getByRole('link').click() 80 + const download = await downloadPromise 81 + expect(download.suggestedFilename()).toBe(options.suggestedFilename) 82 + const downloadPath = await download.path() 83 + expect(readFileSync(downloadPath, 'utf-8')).toBe(options.content) 84 + } 85 + 86 + export async function assertImageAttachment( 87 + page: Page, 88 + options: { 89 + name: string 90 + }, 91 + ) { 92 + const annotation = page.getByRole('note').filter({ hasText: options.name }) 93 + await expect(annotation.getByRole('link')).toHaveAttribute('href', /.+/) 94 + await expect(annotation.getByRole('img')).not.toHaveJSProperty('naturalWidth', 0) 95 + }
-270
test/ui/test/html-report.spec.ts
··· 1 - import type { Page } from '@playwright/test' 2 - import type { PreviewServer } from 'vite' 3 - import { readFileSync } from 'node:fs' 4 - import { Writable } from 'node:stream' 5 - import { expect, test } from '@playwright/test' 6 - import { preview } from 'vite' 7 - import { startVitest } from 'vitest/node' 8 - 9 - const port = 9001 10 - const pageUrl = `http://localhost:${port}/custom/base/` 11 - 12 - test.describe('html report', () => { 13 - let previewServer: PreviewServer 14 - 15 - test.beforeAll(async () => { 16 - // silence Vitest logs 17 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 18 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 19 - // generate vitest html report 20 - await startVitest( 21 - 'test', 22 - [], 23 - { 24 - run: true, 25 - reporters: 'html', 26 - coverage: { 27 - enabled: true, 28 - }, 29 - }, 30 - {}, 31 - { 32 - stdout, 33 - stderr, 34 - }, 35 - ) 36 - 37 - // run vite preview server 38 - previewServer = await preview({ 39 - base: '/custom/base/', 40 - build: { outDir: 'html' }, 41 - preview: { port, strictPort: true }, 42 - }) 43 - }) 44 - 45 - test.afterAll(async () => { 46 - await previewServer?.close() 47 - }) 48 - 49 - test('basic', async ({ page }) => { 50 - const pageErrors: unknown[] = [] 51 - page.on('pageerror', error => pageErrors.push(error)) 52 - 53 - await page.goto(pageUrl) 54 - 55 - // dashboard 56 - await assertTestCounts(page, { pass: 17, fail: 3 }) 57 - 58 - // unhandled errors 59 - await expect(page.getByTestId('unhandled-errors')).toContainText( 60 - 'Vitest caught 2 errors during the test run. This might cause false positive tests. ' 61 - + 'Resolve unhandled errors to make sure your tests are not affected.', 62 - ) 63 - 64 - await expect(page.getByTestId('unhandled-errors-details')).toContainText('Error: error') 65 - await expect(page.getByTestId('unhandled-errors-details')).toContainText('Unknown Error: 1') 66 - 67 - // report 68 - const sample = page.getByTestId('results-panel').getByLabel('sample.test.ts') 69 - await sample.hover() 70 - await sample.getByTestId('btn-open-details').click({ force: true }) 71 - await page.getByText('All tests passed in this file').click() 72 - 73 - // graph tab 74 - await page.getByTestId('btn-graph').click() 75 - await expect(page.locator('[data-testid=graph] text')).toContainText('sample.test.ts') 76 - 77 - // console tab 78 - await page.getByTestId('btn-console').click() 79 - await expect(page.getByTestId('console')).toContainText('log test') 80 - 81 - expect(pageErrors).toEqual([]) 82 - }) 83 - 84 - test('coverage', async ({ page }) => { 85 - await page.goto(pageUrl) 86 - await page.getByLabel('Show coverage').click() 87 - await page.frameLocator('#vitest-ui-coverage').getByRole('heading', { name: 'All files' }).click() 88 - }) 89 - 90 - test('error', async ({ page }) => { 91 - await page.goto(pageUrl) 92 - const sample = page.getByTestId('results-panel').getByLabel('fixtures/error.test.ts') 93 - await sample.hover() 94 - await sample.getByTestId('btn-open-details').click({ force: true }) 95 - await expect(page.getByTestId('diff')).toContainText('- Expected + Received + <style>* {border: 2px solid green};</style>') 96 - }) 97 - 98 - test('annotations in the report tab', async ({ page }) => { 99 - await page.goto(pageUrl) 100 - 101 - await test.step('annotated test', async () => { 102 - const item = page.getByLabel('annotated test') 103 - await item.click({ force: true }) 104 - await page.getByTestId('btn-report').click({ force: true }) 105 - 106 - const annotations = page.getByRole('note') 107 - await expect(annotations).toHaveCount(2) 108 - 109 - await expect(annotations.first()).toContainText('hello world') 110 - await expect(annotations.first()).toContainText('notice') 111 - await expect(annotations.first()).toContainText('fixtures/annotated.test.ts:4:9') 112 - 113 - await expect(annotations.last()).toContainText('second annotation') 114 - await expect(annotations.last()).toContainText('notice') 115 - await expect(annotations.last()).toContainText('fixtures/annotated.test.ts:5:9') 116 - }) 117 - 118 - await test.step('annotated typed test', async () => { 119 - const item = page.getByLabel('annotated typed test') 120 - await item.click({ force: true }) 121 - await page.getByTestId('btn-report').click({ force: true }) 122 - 123 - const annotation = page.getByRole('note') 124 - await expect(annotation).toHaveCount(1) 125 - 126 - await expect(annotation).toContainText('beware!') 127 - await expect(annotation).toContainText('warning') 128 - await expect(annotation).toContainText('fixtures/annotated.test.ts:9:9') 129 - }) 130 - 131 - await test.step('annotated file test', async () => { 132 - const item = page.getByLabel('annotated file test') 133 - await item.click({ force: true }) 134 - await page.getByTestId('btn-report').click({ force: true }) 135 - 136 - const annotation = page.getByRole('note') 137 - await expect(annotation).toHaveCount(1) 138 - 139 - await expect(annotation).toContainText('file annotation') 140 - await expect(annotation).toContainText('notice') 141 - await expect(annotation).toContainText('fixtures/annotated.test.ts:13:9') 142 - await expect(annotation.getByRole('link')).toHaveAttribute('href', /data\/\w+/) 143 - }) 144 - 145 - await test.step('annotated image test', async () => { 146 - const item = page.getByLabel('annotated image test') 147 - await item.click({ force: true }) 148 - await page.getByTestId('btn-report').click({ force: true }) 149 - 150 - const annotation = page.getByRole('note') 151 - await expect(annotation).toHaveCount(1) 152 - 153 - await expect(annotation).toContainText('image annotation') 154 - await expect(annotation).toContainText('notice') 155 - await expect(annotation).toContainText('fixtures/annotated.test.ts:19:9') 156 - await expect(annotation.getByRole('link')).toHaveAttribute('href', /data\/\w+/) 157 - const img = annotation.getByRole('img') 158 - await expect(img).toHaveAttribute('src', /data\/\w+/) 159 - await expect(img).not.toHaveJSProperty('naturalWidth', 0) 160 - }) 161 - 162 - await test.step('annotated with body base64', async () => { 163 - const item = page.getByLabel('annotated with body base64') 164 - await item.click({ force: true }) 165 - await page.getByTestId('btn-report').click({ force: true }) 166 - 167 - const annotation = page.getByRole('note') 168 - await expect(annotation).toHaveCount(1) 169 - 170 - await expect(annotation).toContainText('body base64 annotation') 171 - await expect(annotation).toContainText('notice') 172 - await expect(annotation).toContainText('fixtures/annotated.test.ts:25:9') 173 - 174 - const downloadPromise = page.waitForEvent('download') 175 - await annotation.getByRole('link').click() 176 - const download = await downloadPromise 177 - expect(download.suggestedFilename()).toBe('body-base64-annotation.md') 178 - const downloadPath = await download.path() 179 - const content = readFileSync(downloadPath, 'utf-8') 180 - expect(content).toBe('Hello base64 **markdown**') 181 - }) 182 - 183 - await test.step('annotated with body utf-8', async () => { 184 - const item = page.getByLabel('annotated with body utf-8') 185 - await item.click({ force: true }) 186 - await page.getByTestId('btn-report').click({ force: true }) 187 - 188 - const annotation = page.getByRole('note') 189 - await expect(annotation).toHaveCount(1) 190 - 191 - await expect(annotation).toContainText('body utf-8 annotation') 192 - await expect(annotation).toContainText('notice') 193 - await expect(annotation).toContainText('fixtures/annotated.test.ts:32:9') 194 - 195 - const downloadPromise = page.waitForEvent('download') 196 - await annotation.getByRole('link').click() 197 - const download = await downloadPromise 198 - expect(download.suggestedFilename()).toBe('body-utf-8-annotation.md') 199 - const downloadPath = await download.path() 200 - const content = readFileSync(downloadPath, 'utf-8') 201 - expect(content).toBe('Hello utf-8 **markdown**') 202 - }) 203 - }) 204 - 205 - test('annotations', async ({ page }) => { 206 - await page.goto(pageUrl) 207 - const item = page.getByLabel('fixtures/annotated.test.ts') 208 - await item.hover() 209 - await item.getByTestId('btn-open-details').click({ force: true }) 210 - await page.getByTestId('btn-code').click({ force: true }) 211 - 212 - const annotations = page.getByRole('note') 213 - await expect(annotations).toHaveCount(7) 214 - 215 - await expect(annotations.first()).toHaveText('notice: hello world') 216 - await expect(annotations.nth(1)).toHaveText('notice: second annotation') 217 - await expect(annotations.nth(2)).toHaveText('warning: beware!') 218 - await expect(annotations.nth(3)).toHaveText(/notice: file annotation/) 219 - await expect(annotations.nth(4)).toHaveText('notice: image annotation') 220 - await expect(annotations.nth(5)).toHaveText(/notice: body base64 annotation/) 221 - await expect(annotations.nth(6)).toHaveText(/notice: body utf-8 annotation/) 222 - 223 - await expect(annotations.nth(3).getByRole('link')).toHaveAttribute('href', /data\/\w+/) 224 - await expect(annotations.nth(4).getByRole('link')).toHaveAttribute('href', /data\/\w+/) 225 - await expect(annotations.nth(5).getByRole('link')).toHaveAttribute('href', /^data:text\/markdown;base64,/) 226 - await expect(annotations.nth(6).getByRole('link')).toHaveAttribute('href', /^data:text\/markdown,/) 227 - }) 228 - 229 - test('tags filter', async ({ page }) => { 230 - await page.goto(pageUrl) 231 - 232 - await page.getByPlaceholder('Search...').fill('tag:db') 233 - 234 - // only one test with the tag "db" 235 - await expect(page.getByText('PASS (1)')).toBeVisible() 236 - await expect(page.getByTestId('explorer-item').filter({ hasText: 'has tags' })).toBeVisible() 237 - 238 - await page.getByPlaceholder('Search...').fill('tag:db && !flaky') 239 - await expect(page.getByText('No matched test')).toBeVisible() 240 - 241 - await page.getByPlaceholder('Search...').fill('tag:unknown') 242 - await expect(page.getByText('The tag pattern "unknown" is not defined in the configuration')).toBeVisible() 243 - }) 244 - 245 - test('visual regression in the report tab', async ({ page }) => { 246 - await page.goto(pageUrl) 247 - 248 - await test.step('attachments get processed', async () => { 249 - const item = page.getByLabel('visual regression test') 250 - await item.click({ force: true }) 251 - await page.getByTestId('btn-report').click({ force: true }) 252 - 253 - const artifact = page.getByRole('note') 254 - await expect(artifact).toHaveCount(1) 255 - 256 - await expect(artifact.getByRole('heading')).toContainText('Visual Regression') 257 - await expect(artifact).toContainText('fixtures-browser/visual-regression.test.ts:13:3') 258 - await expect(artifact.getByRole('tablist')).toHaveText('Reference') 259 - await expect(artifact.getByRole('tabpanel').getByRole('link')).toHaveAttribute('href', /data\/\w+\.png/) 260 - const vrImg = artifact.getByRole('tabpanel').getByRole('img') 261 - await expect(vrImg).toHaveAttribute('src', /data\/\w+\.png/) 262 - await expect(vrImg).not.toHaveJSProperty('naturalWidth', 0) 263 - }) 264 - }) 265 - }) 266 - 267 - async function assertTestCounts(page: Page, options: { pass: number; fail: number }) { 268 - await expect.soft(page.getByTestId('tests-entry')) 269 - .toContainText(`${options.pass} Pass ${options.fail} Fail ${options.pass + options.fail} Total`) 270 - }
+5 -20
test/ui/test/trace-stream.spec.ts
··· 1 - import type { Page } from '@playwright/test' 2 1 import type { Vitest } from 'vitest/node' 3 - import assert from 'node:assert' 4 2 import { mkdir, rm, writeFile } from 'node:fs/promises' 5 3 import path from 'node:path' 6 - import { Writable } from 'node:stream' 7 4 import { expect, test } from '@playwright/test' 8 5 import { resolve } from 'pathe' 9 - import { startVitest } from 'vitest/node' 6 + import { getExplorerItem, startVitestUi } from './helper' 10 7 11 8 test.describe('trace stream', () => { 12 9 let vitest: Vitest | undefined 13 10 let baseURL: string 14 11 15 - const root = path.join(import.meta.dirname, '../fixtures-trace-stream') 12 + const root = path.join(import.meta.dirname, '../fixtures/trace-stream') 16 13 const gatesDir = path.join(root, 'node_modules/.vitest-e2e') 17 14 18 15 test.beforeAll(async () => { 19 16 await rm(gatesDir, { recursive: true, force: true }) 20 17 await mkdir(gatesDir, { recursive: true }) 21 18 22 - // silence Vitest logs 23 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 24 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 25 - 26 19 // start standalone to hold off running tests 27 - vitest = await startVitest( 28 - 'test', 29 - undefined, 20 + const server = await startVitestUi( 30 21 { 31 22 root, 32 23 watch: true, ··· 39 30 'import.meta.env.TEST_GATE_FILE': 'true', 40 31 }, 41 32 }, 42 - { stdout, stderr }, 43 33 ) 44 - const address = vitest.vite.httpServer?.address() 45 - assert(address && typeof address === 'object', 'Invalid server address') 46 - baseURL = `http://localhost:${address.port}/__vitest__/` 34 + vitest = server.vitest 35 + baseURL = `${server.url}/__vitest__/` 47 36 }) 48 37 49 38 test.afterAll(async () => { ··· 170 159 ]) 171 160 }) 172 161 }) 173 - 174 - function getExplorerItem(page: Page, name: string) { 175 - return page.getByTestId('explorer-item').and(page.getByLabel(name, { exact: true })) 176 - }
+21 -50
test/ui/test/trace.spec.ts
··· 1 1 import type { Page } from '@playwright/test' 2 2 import type { PreviewServer } from 'vite' 3 3 import type { Vitest } from 'vitest/node' 4 - import assert from 'node:assert' 5 - import { Writable } from 'node:stream' 6 4 import { expect, test } from '@playwright/test' 7 - import { preview } from 'vite' 8 - import { startVitest } from 'vitest/node' 5 + import { assertTestCounts, openExplorerItem, startHtmlReportPreview, startVitestUi } from './helper' 9 6 10 7 test.describe('ui', () => { 11 8 let vitest: Vitest | undefined 12 9 let baseURL: string 13 10 14 11 test.beforeAll(async () => { 15 - // silence Vitest logs 16 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 17 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 18 - vitest = await startVitest( 19 - 'test', 20 - undefined, 21 - { 22 - root: './fixtures-trace', 23 - watch: true, 24 - ui: true, 25 - open: false, 26 - }, 27 - {}, 28 - { stdout, stderr }, 29 - ) 30 - const address = vitest.vite.httpServer?.address() 31 - assert(address && typeof address === 'object', 'Invalid server address') 32 - baseURL = `http://localhost:${address.port}/__vitest__/` 12 + const root = './fixtures/trace' 13 + const server = await startVitestUi({ 14 + root, 15 + watch: true, 16 + ui: true, 17 + open: false, 18 + }) 19 + vitest = server.vitest 20 + baseURL = `${server.url}/__vitest__/` 33 21 }) 34 22 35 23 test.afterAll(async () => { ··· 38 26 39 27 test.beforeEach(async ({ page }) => { 40 28 await page.goto(baseURL) 41 - await testReady(page) 29 + await assertTestCounts(page, { pass: 11, fail: 0 }) 42 30 }) 43 31 44 32 test('basic', async ({ page }) => { ··· 75 63 let baseURL: string 76 64 77 65 test.beforeAll(async () => { 78 - // silence Vitest logs 79 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 80 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 81 - await startVitest( 82 - 'test', 83 - undefined, 66 + const root = './fixtures/trace' 67 + const server = await startHtmlReportPreview( 84 68 { 85 - root: './fixtures-trace', 69 + root, 86 70 run: true, 87 71 ui: false, 88 72 reporters: 'html', ··· 93 77 }, 94 78 }, 95 79 }, 96 - {}, 97 - { stdout, stderr }, 80 + { 81 + root, 82 + build: { outDir: 'html' }, 83 + }, 98 84 ) 99 - previewServer = await preview({ 100 - root: './fixtures-trace', 101 - build: { outDir: 'html' }, 102 - }) 103 - const address = previewServer.httpServer?.address() 104 - assert(address && typeof address === 'object', 'Invalid server address') 105 - baseURL = `http://localhost:${address.port}/` 85 + previewServer = server.previewServer 86 + baseURL = `${server.url}/` 106 87 }) 107 88 108 89 test.afterAll(async () => { ··· 111 92 112 93 test.beforeEach(async ({ page }) => { 113 94 await page.goto(baseURL) 114 - await testReady(page) 95 + await assertTestCounts(page, { pass: 11, fail: 0 }) 115 96 }) 116 97 117 98 test('basic', async ({ page }) => { ··· 143 124 await testAttempts(page) 144 125 }) 145 126 }) 146 - 147 - async function testReady(page: Page) { 148 - const count = 11 149 - await expect.soft(page.getByTestId('tests-entry')) 150 - .toContainText(`${count} Pass 0 Fail ${count} Total`) 151 - } 152 - 153 - async function openExplorerItem(page: Page, name: string) { 154 - await page.getByTestId('explorer-item').and(page.getByLabel(name, { exact: true })).click() 155 - } 156 127 157 128 async function testBasic(page: Page) { 158 129 // selecting test case opens trace viewer
-70
test/ui/test/ui-security.spec.ts
··· 1 - import type { Vitest } from 'vitest/node' 2 - import { Writable } from 'node:stream' 3 - import { expect, test } from '@playwright/test' 4 - import { startVitest } from 'vitest/node' 5 - 6 - const port = 9002 7 - const pageUrl = `http://localhost:${port}/__vitest__/` 8 - 9 - test.describe('ui', () => { 10 - let vitest: Vitest | undefined 11 - 12 - test.beforeAll(async () => { 13 - // silence Vitest logs 14 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 15 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 16 - vitest = await startVitest('test', [], { 17 - watch: true, 18 - ui: true, 19 - open: false, 20 - api: { 21 - port, 22 - allowExec: false, 23 - allowWrite: false, 24 - }, 25 - reporters: [], 26 - }, {}, { 27 - stdout, 28 - stderr, 29 - }) 30 - expect(vitest).toBeDefined() 31 - }) 32 - 33 - test.afterAll(async () => { 34 - await vitest?.close() 35 - }) 36 - 37 - test('cannot execute files from the ui', async ({ page }) => { 38 - await page.goto(pageUrl) 39 - 40 - await expect(page.getByTestId('btn-run-all')).toBeDisabled() 41 - 42 - const item = page.getByTestId('explorer-item').nth(0) 43 - await item.hover() 44 - await expect(item.getByTestId('btn-run-test')).toBeDisabled() 45 - 46 - await page.getByPlaceholder('Search...').fill('snapshot') 47 - 48 - const snapshotItem = page.getByTestId('explorer-item').filter({ hasText: 'snapshot.test.ts' }) 49 - await snapshotItem.hover() 50 - await expect(snapshotItem.getByTestId('btn-fix-snapshot')).not.toBeVisible() 51 - }) 52 - 53 - test('cannot write files', async ({ page }) => { 54 - await page.goto(pageUrl) 55 - 56 - const item = page.getByTestId('explorer-item').nth(0) 57 - await item.hover() 58 - await item.getByTestId('btn-open-details').click() 59 - 60 - await page.getByText('Code').click() 61 - 62 - const editor = page.getByTestId('btn-code') 63 - await expect(editor).toBeVisible() 64 - 65 - await editor.click() 66 - await page.keyboard.type('\n// some comment') 67 - 68 - await expect(editor).not.toContainText('// some comment') 69 - }) 70 - })
+545 -343
test/ui/test/ui.spec.ts
··· 1 1 import type { Page } from '@playwright/test' 2 + import type { PreviewServer } from 'vite' 2 3 import type { Vitest } from 'vitest/node' 3 - import { readFileSync } from 'node:fs' 4 - import { Writable } from 'node:stream' 5 4 import { expect, test } from '@playwright/test' 6 - import { startVitest } from 'vitest/node' 7 - 8 - const port = 9000 9 - const pageUrl = `http://localhost:${port}/__vitest__/` 5 + import { assertDownloadAttachment, assertImageAttachment, assertTestCounts, getExplorerItem, openExplorerFileItem, startHtmlReportPreview, startVitestUi } from './helper' 10 6 11 7 test.describe('ui', () => { 12 8 let vitest: Vitest | undefined 9 + let pageUrl: string 13 10 14 11 test.beforeAll(async () => { 15 - // silence Vitest logs 16 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 17 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 18 - vitest = await startVitest('test', [], { 12 + const server = await startVitestUi({ 13 + root: './fixtures/main', 19 14 watch: true, 20 15 ui: true, 21 16 open: false, 22 - api: { port }, 23 17 coverage: { enabled: true }, 24 18 reporters: [], 25 - }, {}, { 26 - stdout, 27 - stderr, 28 19 }) 29 - expect(vitest).toBeDefined() 20 + vitest = server.vitest 21 + pageUrl = `${server.url}/__vitest__/` 30 22 }) 31 23 32 24 test.afterAll(async () => { 33 25 await vitest?.close() 34 26 }) 35 27 36 - test('security', async ({ page }, testInfo) => { 37 - const response = await page.goto('https://example.com/', { timeout: 5000 }).catch(() => null) 38 - 39 - testInfo.skip(!response, 'External resource is not available') 40 - 41 - // request html 42 - const htmlResult = await page.evaluate(async (pageUrl) => { 43 - try { 44 - const res = await fetch(pageUrl) 45 - return res.status 46 - } 47 - catch (e) { 48 - return e instanceof Error ? e.message : e 49 - } 50 - }, pageUrl) 51 - expect(htmlResult).toBe('Failed to fetch') 52 - 53 - // request websocket 54 - const wsResult = await page.evaluate(async (pageUrl) => { 55 - const ws = new WebSocket(new URL('/__vitest_api__', pageUrl)) 56 - return new Promise((resolve) => { 57 - ws.addEventListener('open', () => { 58 - resolve('open') 59 - }) 60 - ws.addEventListener('error', () => { 61 - resolve('error') 62 - }) 63 - }) 64 - }, pageUrl) 65 - expect(wsResult).toBe('error') 28 + test('basic', async ({ page }) => { 29 + await testBasic(page, pageUrl) 66 30 }) 67 31 68 - test('basic', async ({ page }) => { 69 - const pageErrors: unknown[] = [] 70 - page.on('pageerror', error => pageErrors.push(error)) 71 - 72 - await page.goto(pageUrl) 73 - 74 - // dashboard 75 - await assertTestCounts(page, { pass: 17, fail: 3 }) 76 - 77 - // unhandled errors 78 - await expect(page.getByTestId('unhandled-errors')).toContainText( 79 - 'Vitest caught 2 errors during the test run. This might cause false positive tests. ' 80 - + 'Resolve unhandled errors to make sure your tests are not affected.', 81 - ) 82 - 83 - await expect(page.getByTestId('unhandled-errors-details')).toContainText('Error: error') 84 - await expect(page.getByTestId('unhandled-errors-details')).toContainText('Unknown Error: 1') 85 - 86 - // report 87 - const sample = page.getByTestId('results-panel').getByLabel('sample.test.ts') 88 - await sample.hover() 89 - await sample.getByTestId('btn-open-details').click({ force: true }) 90 - await page.getByText('All tests passed in this file').click() 91 - 92 - // graph tab 93 - await page.getByTestId('btn-graph').click() 94 - await expect(page.locator('[data-testid=graph] text')).toContainText('sample.test.ts') 95 - 96 - // console tab 97 - await page.getByTestId('btn-console').click() 98 - await expect(page.getByTestId('console')).toContainText('log test') 99 - 100 - expect(pageErrors).toEqual([]) 32 + test('cross origin access', async ({ page }) => { 33 + await testCrossOriginAccess(page, pageUrl) 101 34 }) 102 35 103 36 test('coverage', async ({ page }) => { 104 37 await page.goto(pageUrl) 105 - await page.getByLabel('Show coverage').click() 106 - await page.frameLocator('#vitest-ui-coverage').getByRole('heading', { name: 'All files' }).click() 38 + await testCoverage(page) 107 39 }) 108 40 109 41 test('console', async ({ page }) => { 110 42 await page.goto(pageUrl) 111 - const item = page.getByLabel('fixtures/console.test.ts') 112 - await item.hover() 113 - await item.getByTestId('btn-open-details').click({ force: true }) 114 - await page.getByTestId('btn-console').click() 115 - await page.getByText('/(?<char>\\w)/').click() 116 - 117 - expect(await page.getByText('beforeAll').all()).toHaveLength(6) 118 - expect(await page.getByText('afterAll').all()).toHaveLength(6) 119 - }) 120 - 121 - test('annotations in the report tab', async ({ page }) => { 122 - await page.goto(pageUrl) 123 - 124 - await test.step('annotated test', async () => { 125 - const item = page.getByLabel('annotated test') 126 - await item.click({ force: true }) 127 - await page.getByTestId('btn-report').click({ force: true }) 128 - 129 - const annotations = page.getByRole('note') 130 - await expect(annotations).toHaveCount(2) 131 - 132 - await expect(annotations.first()).toContainText('hello world') 133 - await expect(annotations.first()).toContainText('notice') 134 - await expect(annotations.first()).toContainText('fixtures/annotated.test.ts:4:9') 135 - 136 - await expect(annotations.last()).toContainText('second annotation') 137 - await expect(annotations.last()).toContainText('notice') 138 - await expect(annotations.last()).toContainText('fixtures/annotated.test.ts:5:9') 139 - }) 140 - 141 - await test.step('annotated typed test', async () => { 142 - const item = page.getByLabel('annotated typed test') 143 - await item.click({ force: true }) 144 - await page.getByTestId('btn-report').click({ force: true }) 145 - 146 - const annotation = page.getByRole('note') 147 - await expect(annotation).toHaveCount(1) 148 - 149 - await expect(annotation).toContainText('beware!') 150 - await expect(annotation).toContainText('warning') 151 - await expect(annotation).toContainText('fixtures/annotated.test.ts:9:9') 152 - }) 153 - 154 - await test.step('annotated file test', async () => { 155 - const item = page.getByLabel('annotated file test') 156 - await item.click({ force: true }) 157 - await page.getByTestId('btn-report').click({ force: true }) 158 - 159 - const annotation = page.getByRole('note') 160 - await expect(annotation).toHaveCount(1) 161 - 162 - await expect(annotation).toContainText('file annotation') 163 - await expect(annotation).toContainText('notice') 164 - await expect(annotation).toContainText('fixtures/annotated.test.ts:13:9') 165 - await expect(annotation.getByRole('link')).toHaveAttribute('href', /__vitest_attachment__\?path=/) 166 - }) 167 - 168 - await test.step('annotated image test', async () => { 169 - const item = page.getByLabel('annotated image test') 170 - await item.click({ force: true }) 171 - await page.getByTestId('btn-report').click({ force: true }) 172 - 173 - const annotation = page.getByRole('note') 174 - await expect(annotation).toHaveCount(1) 175 - 176 - await expect(annotation).toContainText('image annotation') 177 - await expect(annotation).toContainText('notice') 178 - await expect(annotation).toContainText('fixtures/annotated.test.ts:19:9') 179 - await expect(annotation.getByRole('link')).toHaveAttribute('href', /__vitest_attachment__\?path=/) 180 - await expect(annotation.getByRole('img')).toHaveAttribute('src', /__vitest_attachment__\?path=/) 181 - }) 182 - 183 - await test.step('annotated with body base64', async () => { 184 - const item = page.getByLabel('annotated with body base64') 185 - await item.click({ force: true }) 186 - await page.getByTestId('btn-report').click({ force: true }) 187 - 188 - const annotation = page.getByRole('note') 189 - await expect(annotation).toHaveCount(1) 190 - 191 - await expect(annotation).toContainText('body base64 annotation') 192 - await expect(annotation).toContainText('notice') 193 - await expect(annotation).toContainText('fixtures/annotated.test.ts:25:9') 194 - 195 - const downloadPromise = page.waitForEvent('download') 196 - await annotation.getByRole('link').click() 197 - const download = await downloadPromise 198 - expect(download.suggestedFilename()).toBe('body-base64-annotation.md') 199 - const downloadPath = await download.path() 200 - const content = readFileSync(downloadPath, 'utf-8') 201 - expect(content).toBe('Hello base64 **markdown**') 202 - }) 203 - 204 - await test.step('annotated with body utf-8', async () => { 205 - const item = page.getByLabel('annotated with body utf-8') 206 - await item.click({ force: true }) 207 - await page.getByTestId('btn-report').click({ force: true }) 208 - 209 - const annotation = page.getByRole('note') 210 - await expect(annotation).toHaveCount(1) 211 - 212 - await expect(annotation).toContainText('body utf-8 annotation') 213 - await expect(annotation).toContainText('notice') 214 - await expect(annotation).toContainText('fixtures/annotated.test.ts:32:9') 215 - 216 - const downloadPromise = page.waitForEvent('download') 217 - await annotation.getByRole('link').click() 218 - const download = await downloadPromise 219 - expect(download.suggestedFilename()).toBe('body-utf-8-annotation.md') 220 - const downloadPath = await download.path() 221 - const content = readFileSync(downloadPath, 'utf-8') 222 - expect(content).toBe('Hello utf-8 **markdown**') 223 - }) 224 - }) 225 - 226 - test('annotations in the editor tab', async ({ page }) => { 227 - await page.goto(pageUrl) 228 - const item = page.getByLabel('fixtures/annotated.test.ts') 229 - await item.hover() 230 - await item.getByTestId('btn-open-details').click({ force: true }) 231 - await page.getByTestId('btn-code').click({ force: true }) 232 - 233 - const annotations = page.getByRole('note') 234 - await expect(annotations).toHaveCount(7) 235 - 236 - await expect(annotations.first()).toHaveText('notice: hello world') 237 - await expect(annotations.nth(1)).toHaveText('notice: second annotation') 238 - await expect(annotations.nth(2)).toHaveText('warning: beware!') 239 - await expect(annotations.nth(3)).toHaveText(/notice: file annotation/) 240 - await expect(annotations.nth(4)).toHaveText('notice: image annotation') 241 - await expect(annotations.nth(5)).toHaveText(/notice: body base64 annotation/) 242 - await expect(annotations.nth(6)).toHaveText(/notice: body utf-8 annotation/) 243 - 244 - await expect(annotations.nth(3).getByRole('link')).toHaveAttribute('href', /__vitest_attachment__\?path=/) 245 - await expect(annotations.nth(4).getByRole('link')).toHaveAttribute('href', /__vitest_attachment__\?path=/) 246 - await expect(annotations.nth(5).getByRole('link')).toHaveAttribute('href', /^data:text\/markdown;base64,/) 247 - await expect(annotations.nth(6).getByRole('link')).toHaveAttribute('href', /^data:text\/markdown,/) 43 + await testConsole(page) 248 44 }) 249 45 250 46 test('error', async ({ page }) => { 251 47 await page.goto(pageUrl) 252 - const item = page.getByLabel('fixtures/error.test.ts') 253 - await item.hover() 254 - await item.getByTestId('btn-open-details').click({ force: true }) 255 - await expect(page.getByTestId('diff')).toContainText('- Expected + Received + <style>* {border: 2px solid green};</style>') 256 - 257 - await getExplorerItem(page, 'colored error message').click() 258 - await expect(page.getByTestId('report')).toHaveText('Error: this-is-blue - /fixtures/error.test.ts:12:17') 48 + await testError(page) 259 49 }) 260 50 261 - test('file-filter', async ({ page }) => { 51 + test('filter', async ({ page }) => { 262 52 await page.goto(pageUrl) 263 - 264 - // match all files when no filter 265 - await page.getByPlaceholder('Search...').fill('') 266 - await page.getByText('PASS (6)').click() 267 - await expect(page.getByTestId('results-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeVisible() 268 - 269 - // match nothing 270 - await page.getByPlaceholder('Search...').fill('nothing') 271 - await page.getByText('No matched test').click() 272 - 273 - // searching "add" will match "sample.test.ts" since it includes a test case named "add" 274 - await page.getByPlaceholder('Search...').fill('add') 275 - await page.getByText('PASS (1)').click() 276 - await expect(page.getByTestId('results-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeVisible() 277 - 278 - // match only failing files when fail filter applied 279 - await page.getByPlaceholder('Search...').fill('') 280 - await page.getByText(/^Fail$/, { exact: true }).click() 281 - await page.getByText('FAIL (2)').click() 282 - await expect(page.getByTestId('results-panel').getByText('fixtures/error.test.ts', { exact: true })).toBeVisible() 283 - await expect(page.getByTestId('results-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeHidden() 284 - 285 - // match only pass files when fail filter applied 286 - await page.getByPlaceholder('Search...').fill('console') 287 - await page.getByText(/^Fail$/, { exact: true }).click() 288 - await page.locator('span').filter({ hasText: /^Pass$/ }).click() 289 - await page.getByText('PASS (1)').click() 290 - await expect(page.getByTestId('results-panel').getByText('fixtures/console.test.ts', { exact: true })).toBeVisible() 291 - await expect(page.getByTestId('results-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeHidden() 292 - 293 - // html entities in task names are escaped 294 - await page.locator('span').filter({ hasText: /^Pass$/ }).click() 295 - await page.getByPlaceholder('Search...').fill('<MyComponent />') 296 - // for some reason, the tree is collapsed by default: we need to click on the nav buttons to expand it 297 - await page.getByTestId('collapse-all').click() 298 - await page.getByTestId('expand-all').click() 299 - await expect(page.getByText('<MyComponent />')).toBeVisible() 300 - await expect(page.getByTestId('results-panel').getByText('fixtures/task-name.test.ts', { exact: true })).toBeVisible() 301 - 302 - // html entities in task names are escaped 303 - await page.getByPlaceholder('Search...').fill('<>\'"') 304 - await expect(page.getByText('<>\'"')).toBeVisible() 305 - await expect(page.getByTestId('results-panel').getByText('fixtures/task-name.test.ts', { exact: true })).toBeVisible() 306 - 307 - // pass files with special chars 308 - await page.getByPlaceholder('Search...').fill('char () - Square root of nine (9)') 309 - await expect(page.getByText('char () - Square root of nine (9)')).toBeVisible() 310 - const testItem = page.getByTestId('explorer-item').filter({ hasText: 'char () - Square root of nine (9)' }) 311 - await testItem.hover() 312 - await testItem.getByLabel('Run current test').click() 313 - await expect(page.getByText('The test has passed without any errors')).toBeVisible() 53 + await testFilter(page, { mode: 'ui' }) 314 54 }) 315 55 316 56 test('tags filter', async ({ page }) => { 317 57 await page.goto(pageUrl) 318 - 319 - await page.getByPlaceholder('Search...').fill('tag:db') 320 - 321 - // only one test with the tag "db" 322 - await expect(page.getByText('PASS (1)')).toBeVisible() 323 - await expect(page.getByTestId('explorer-item').filter({ hasText: 'has tags' })).toBeVisible() 324 - 325 - await page.getByPlaceholder('Search...').fill('tag:db && !flaky') 326 - await expect(page.getByText('No matched test')).toBeVisible() 327 - 328 - await page.getByPlaceholder('Search...').fill('tag:unknown') 329 - await expect(page.getByText('The tag pattern "unknown" is not defined in the configuration')).toBeVisible() 58 + await testTagsFilter(page) 330 59 }) 331 60 332 61 test('dashboard entries filter tests correctly', async ({ page }) => { 333 62 await page.goto(pageUrl) 63 + await testDashboardFilter(page) 64 + }) 334 65 335 - // Initial state should show all tests 336 - await expect(page.getByTestId('pass-entry')).toBeVisible() 337 - await expect(page.getByTestId('fail-entry')).toBeVisible() 338 - await expect(page.getByTestId('total-entry')).toBeVisible() 66 + test('annotations in the report tab', async ({ page }) => { 67 + await page.goto(pageUrl) 68 + await testAnnotationsInReport(page) 69 + }) 339 70 340 - // Click "Pass" entry and verify only passing tests are shown 341 - await page.getByTestId('pass-entry').click() 342 - await expect(page.getByLabel(/pass/i)).toBeChecked() 343 - 344 - // Click "Fail" entry and verify only failing tests are shown 345 - await page.getByTestId('fail-entry').click() 346 - await expect(page.getByLabel(/fail/i)).toBeChecked() 347 - 348 - // Click "Skip" entry if there are skipped tests 349 - if (await page.getByTestId('skipped-entry').isVisible()) { 350 - await page.getByTestId('skipped-entry').click() 351 - await expect(page.getByLabel(/skip/i)).toBeChecked() 352 - } 353 - 354 - // Click "Total" entry to reset filters and show all tests again 355 - await page.getByTestId('total-entry').click() 356 - await expect(page.getByLabel(/pass/i)).not.toBeChecked() 357 - await expect(page.getByLabel(/fail/i)).not.toBeChecked() 358 - await expect(page.getByLabel(/skip/i)).not.toBeChecked() 71 + test('annotations in the editor tab', async ({ page }) => { 72 + await page.goto(pageUrl) 73 + await testAnnotationsInCode(page) 359 74 }) 360 75 361 76 test('visual regression in the report tab', async ({ page }) => { 362 77 await page.goto(pageUrl) 78 + await testVisualRegression(page) 79 + }) 363 80 364 - await test.step('attachments get processed', async () => { 365 - const item = page.getByLabel('visual regression test') 366 - await item.click({ force: true }) 367 - await page.getByTestId('btn-report').click({ force: true }) 81 + test('can edit file', async ({ page }) => { 82 + await page.goto(pageUrl) 83 + await testWriteFile(page, { enabled: true }) 84 + }) 368 85 369 - const artifact = page.getByRole('note') 370 - await expect(artifact).toHaveCount(1) 371 - 372 - await expect(artifact.getByRole('heading')).toContainText('Visual Regression') 373 - await expect(artifact).toContainText('fixtures-browser/visual-regression.test.ts:13:3') 374 - await expect(artifact.getByRole('tablist')).toHaveText('Reference') 375 - await expect(artifact.getByRole('tabpanel').getByRole('link')).toHaveAttribute('href', /__vitest_attachment__\?path=.*?\.png/) 376 - await expect(artifact.getByRole('tabpanel').getByRole('img')).toHaveAttribute('src', /__vitest_attachment__\?path=.*?\.png/) 377 - }) 86 + test('can execute', async ({ page }) => { 87 + await page.goto(pageUrl) 88 + await testExecute(page, { mode: 'ui' }) 378 89 }) 379 90 }) 380 91 381 - // TODO: consolidate in https://github.com/vitest-dev/vitest/pull/10237 382 - function getExplorerItem(page: Page, name: string) { 383 - return page.getByTestId('explorer-item').and(page.getByLabel(name, { exact: true })) 92 + test.describe('html report', () => { 93 + let previewServer: PreviewServer 94 + let pageUrl: string 95 + 96 + test.beforeAll(async () => { 97 + const server = await startHtmlReportPreview( 98 + { 99 + root: './fixtures/main', 100 + run: true, 101 + reporters: 'html', 102 + coverage: { 103 + enabled: true, 104 + }, 105 + }, 106 + { 107 + root: './fixtures/main', 108 + base: '/custom/base/', 109 + build: { outDir: 'html' }, 110 + }, 111 + ) 112 + previewServer = server.previewServer 113 + pageUrl = `${server.url}/custom/base/` 114 + }) 115 + 116 + test.afterAll(async () => { 117 + await previewServer?.close() 118 + }) 119 + 120 + test('basic', async ({ page }) => { 121 + await testBasic(page, pageUrl) 122 + }) 123 + 124 + test('coverage', async ({ page }) => { 125 + await page.goto(pageUrl) 126 + await testCoverage(page) 127 + }) 128 + 129 + test('console', async ({ page }) => { 130 + await page.goto(pageUrl) 131 + await testConsole(page) 132 + }) 133 + 134 + test('error', async ({ page }) => { 135 + await page.goto(pageUrl) 136 + await testError(page) 137 + }) 138 + 139 + test('filter', async ({ page }) => { 140 + await page.goto(pageUrl) 141 + await testFilter(page, { mode: 'static' }) 142 + }) 143 + 144 + test('tags filter', async ({ page }) => { 145 + await page.goto(pageUrl) 146 + await testTagsFilter(page) 147 + }) 148 + 149 + test('dashboard entries filter tests correctly', async ({ page }) => { 150 + await page.goto(pageUrl) 151 + await testDashboardFilter(page) 152 + }) 153 + 154 + test('annotations in the report tab', async ({ page }) => { 155 + await page.goto(pageUrl) 156 + await testAnnotationsInReport(page) 157 + }) 158 + 159 + test('annotations in the editor tab', async ({ page }) => { 160 + await page.goto(pageUrl) 161 + await testAnnotationsInCode(page) 162 + }) 163 + 164 + test('visual regression in the report tab', async ({ page }) => { 165 + await page.goto(pageUrl) 166 + await testVisualRegression(page) 167 + }) 168 + 169 + test('cannot edit file', async ({ page }) => { 170 + await page.goto(pageUrl) 171 + await testWriteFile(page, { enabled: false }) 172 + }) 173 + 174 + test('cannot execute', async ({ page }) => { 175 + await page.goto(pageUrl) 176 + await testExecute(page, { mode: 'static' }) 177 + }) 178 + }) 179 + 180 + async function testBasic(page: Page, pageUrl: string) { 181 + const pageErrors: unknown[] = [] 182 + page.on('pageerror', error => pageErrors.push(error)) 183 + 184 + await page.goto(pageUrl) 185 + 186 + // dashboard 187 + await assertTestCounts(page, { pass: 17, fail: 3 }) 188 + 189 + // unhandled errors 190 + await expect(page.getByTestId('unhandled-errors')).toContainText( 191 + 'Vitest caught 2 errors during the test run. This might cause false positive tests. ' 192 + + 'Resolve unhandled errors to make sure your tests are not affected.', 193 + ) 194 + 195 + await expect(page.getByTestId('unhandled-errors-details')).toContainText('Error: error') 196 + await expect(page.getByTestId('unhandled-errors-details')).toContainText('Unknown Error: 1') 197 + 198 + // report 199 + await openExplorerFileItem(page, 'sample.test.ts') 200 + await page.getByText('All tests passed in this file').click() 201 + 202 + // graph tab 203 + await page.getByTestId('btn-graph').click() 204 + await expect(page.locator('[data-testid=graph] text')).toContainText('sample.test.ts') 205 + 206 + // console tab 207 + await page.getByTestId('btn-console').click() 208 + await expect(page.getByTestId('console')).toContainText('log test') 209 + 210 + expect(pageErrors).toEqual([]) 384 211 } 385 212 386 - async function assertTestCounts(page: Page, options: { pass: number; fail: number }) { 387 - await expect.soft(page.getByTestId('tests-entry')) 388 - .toContainText(`${options.pass} Pass ${options.fail} Fail ${options.pass + options.fail} Total`) 213 + async function testCoverage(page: Page) { 214 + await page.getByLabel('Show coverage').click() 215 + await page.frameLocator('#vitest-ui-coverage').getByRole('heading', { name: 'All files' }).click() 216 + } 217 + 218 + async function testAnnotationsInReport(page: Page) { 219 + await test.step('annotated test', async () => { 220 + await getExplorerItem(page, 'annotated test').click() 221 + 222 + const annotations = page.getByRole('note') 223 + await expect(annotations).toHaveCount(2) 224 + 225 + await expect(annotations.first()).toContainText('hello world') 226 + await expect(annotations.first()).toContainText('notice') 227 + await expect(annotations.first()).toContainText('annotated.test.ts:4:9') 228 + 229 + await expect(annotations.last()).toContainText('second annotation') 230 + await expect(annotations.last()).toContainText('notice') 231 + await expect(annotations.last()).toContainText('annotated.test.ts:5:9') 232 + }) 233 + 234 + await test.step('annotated typed test', async () => { 235 + await getExplorerItem(page, 'annotated typed test').click() 236 + 237 + const annotation = page.getByRole('note') 238 + await expect(annotation).toHaveCount(1) 239 + 240 + await expect(annotation).toContainText('beware!') 241 + await expect(annotation).toContainText('warning') 242 + await expect(annotation).toContainText('annotated.test.ts:9:9') 243 + }) 244 + 245 + await test.step('annotated file test', async () => { 246 + await getExplorerItem(page, 'annotated file test').click() 247 + 248 + const annotation = page.getByRole('note') 249 + await expect(annotation).toHaveCount(1) 250 + 251 + await expect(annotation).toContainText('file annotation') 252 + await expect(annotation).toContainText('notice') 253 + await expect(annotation).toContainText('annotated.test.ts:13:9') 254 + await assertDownloadAttachment(page, { 255 + name: 'file annotation', 256 + suggestedFilename: 'file-annotation.txt', 257 + content: 'hello world\n', 258 + }) 259 + }) 260 + 261 + await test.step('annotated image test', async () => { 262 + await getExplorerItem(page, 'annotated image test').click() 263 + 264 + const annotation = page.getByRole('note') 265 + await expect(annotation).toHaveCount(1) 266 + 267 + await expect(annotation).toContainText('image annotation') 268 + await expect(annotation).toContainText('notice') 269 + await expect(annotation).toContainText('annotated.test.ts:19:9') 270 + await assertImageAttachment(page, { 271 + name: 'image annotation', 272 + }) 273 + }) 274 + 275 + await test.step('annotated with body base64', async () => { 276 + await getExplorerItem(page, 'annotated with body base64').click() 277 + 278 + const annotation = page.getByRole('note') 279 + await expect(annotation).toHaveCount(1) 280 + 281 + await expect(annotation).toContainText('body base64 annotation') 282 + await expect(annotation).toContainText('notice') 283 + await expect(annotation).toContainText('annotated.test.ts:25:9') 284 + await assertDownloadAttachment(page, { 285 + name: 'body base64 annotation', 286 + suggestedFilename: 'body-base64-annotation.md', 287 + content: 'Hello base64 **markdown**', 288 + }) 289 + }) 290 + 291 + await test.step('annotated with body utf-8', async () => { 292 + await getExplorerItem(page, 'annotated with body utf-8').click() 293 + 294 + const annotation = page.getByRole('note') 295 + await expect(annotation).toHaveCount(1) 296 + 297 + await expect(annotation).toContainText('body utf-8 annotation') 298 + await expect(annotation).toContainText('notice') 299 + await expect(annotation).toContainText('annotated.test.ts:32:9') 300 + await assertDownloadAttachment(page, { 301 + name: 'body utf-8 annotation', 302 + suggestedFilename: 'body-utf-8-annotation.md', 303 + content: 'Hello utf-8 **markdown**', 304 + }) 305 + }) 306 + } 307 + 308 + async function testAnnotationsInCode(page: Page) { 309 + await openExplorerFileItem(page, 'annotated.test.ts') 310 + await page.getByTestId('btn-code').click() 311 + 312 + const annotations = page.getByRole('note') 313 + await expect(annotations).toHaveCount(7) 314 + 315 + await expect(annotations.first()).toHaveText('notice: hello world') 316 + await expect(annotations.nth(1)).toHaveText('notice: second annotation') 317 + await expect(annotations.nth(2)).toHaveText('warning: beware!') 318 + await expect(annotations.nth(3)).toHaveText(/notice: file annotation/) 319 + await expect(annotations.nth(4)).toHaveText('notice: image annotation') 320 + await expect(annotations.nth(5)).toHaveText(/notice: body base64 annotation/) 321 + await expect(annotations.nth(6)).toHaveText(/notice: body utf-8 annotation/) 322 + 323 + await assertDownloadAttachment(page, { 324 + name: 'file annotation', 325 + suggestedFilename: 'file-annotation.txt', 326 + content: 'hello world\n', 327 + }) 328 + await assertDownloadAttachment(page, { 329 + name: 'body base64 annotation', 330 + suggestedFilename: 'body-base64-annotation.md', 331 + content: 'Hello base64 **markdown**', 332 + }) 333 + await assertDownloadAttachment(page, { 334 + name: 'body utf-8 annotation', 335 + suggestedFilename: 'body-utf-8-annotation.md', 336 + content: 'Hello utf-8 **markdown**', 337 + }) 338 + await assertImageAttachment(page, { 339 + name: 'image annotation', 340 + }) 341 + } 342 + 343 + async function testConsole(page: Page) { 344 + await openExplorerFileItem(page, 'console.test.ts') 345 + await page.getByTestId('btn-console').click() 346 + await page.getByText('/(?<char>\\w)/').click() 347 + 348 + await expect(page.getByText('beforeAll')).toHaveCount(6) 349 + await expect(page.getByText('afterAll')).toHaveCount(6) 350 + } 351 + 352 + async function testError(page: Page) { 353 + await openExplorerFileItem(page, 'error.test.ts') 354 + await expect(page.getByTestId('diff')).toContainText('- Expected + Received + <style>* {border: 2px solid green};</style>') 355 + 356 + await getExplorerItem(page, 'colored error message').click() 357 + await expect(page.getByTestId('report')).toHaveText('Error: this-is-blue - /node/error.test.ts:12:17') 358 + } 359 + 360 + async function testTagsFilter(page: Page) { 361 + await page.getByPlaceholder('Search...').fill('tag:db') 362 + 363 + // only one test with the tag "db" 364 + await expect(page.getByText('PASS (1)')).toBeVisible() 365 + await expect(page.getByTestId('explorer-item').filter({ hasText: 'has tags' })).toBeVisible() 366 + 367 + await page.getByPlaceholder('Search...').fill('tag:db && !flaky') 368 + await expect(page.getByText('No matched test')).toBeVisible() 369 + 370 + await page.getByPlaceholder('Search...').fill('tag:unknown') 371 + await expect(page.getByText('The tag pattern "unknown" is not defined in the configuration')).toBeVisible() 372 + } 373 + 374 + async function testVisualRegression(page: Page) { 375 + await getExplorerItem(page, 'visual regression test').click() 376 + 377 + const artifact = page.getByRole('note') 378 + await expect(artifact).toHaveCount(1) 379 + 380 + await expect(artifact.getByRole('heading')).toContainText('Visual Regression') 381 + await expect(artifact).toContainText('visual-regression.test.ts:7:3') 382 + await expect(artifact.getByRole('tablist')).toHaveText('Reference') 383 + await expect(artifact.getByRole('tabpanel').getByRole('img')).not.toHaveJSProperty('naturalWidth', 0) 384 + } 385 + 386 + async function testDashboardFilter(page: Page) { 387 + // Initial state should show all tests 388 + await expect(page.getByTestId('pass-entry')).toBeVisible() 389 + await expect(page.getByTestId('fail-entry')).toBeVisible() 390 + await expect(page.getByTestId('total-entry')).toBeVisible() 391 + 392 + // Click "Pass" entry and verify only passing tests are shown 393 + await page.getByTestId('pass-entry').click() 394 + await expect(page.getByLabel(/pass/i)).toBeChecked() 395 + 396 + // Click "Fail" entry and verify only failing tests are shown 397 + await page.getByTestId('fail-entry').click() 398 + await expect(page.getByLabel(/fail/i)).toBeChecked() 399 + 400 + // TODO: test skip 401 + // Click "Skip" entry if there are skipped tests 402 + if (await page.getByTestId('skipped-entry').isVisible()) { 403 + await page.getByTestId('skipped-entry').click() 404 + await expect(page.getByLabel(/skip/i)).toBeChecked() 405 + } 406 + 407 + // Click "Total" entry to reset filters and show all tests again 408 + await page.getByTestId('total-entry').click() 409 + await expect(page.getByLabel(/pass/i)).not.toBeChecked() 410 + await expect(page.getByLabel(/fail/i)).not.toBeChecked() 411 + await expect(page.getByLabel(/skip/i)).not.toBeChecked() 412 + } 413 + 414 + async function testFilter(page: Page, options: { mode: 'ui' | 'static' }) { 415 + // match all files when no filter 416 + await page.getByPlaceholder('Search...').fill('') 417 + await page.getByText('PASS (6)').click() 418 + await expect(page.getByTestId('results-panel').getByText('sample.test.ts', { exact: true })).toBeVisible() 419 + 420 + // match nothing 421 + await page.getByPlaceholder('Search...').fill('nothing') 422 + await page.getByText('No matched test').click() 423 + 424 + // searching "add" will match "sample.test.ts" since it includes a test case named "add" 425 + await page.getByPlaceholder('Search...').fill('add') 426 + await page.getByText('PASS (1)').click() 427 + await expect(page.getByTestId('results-panel').getByText('sample.test.ts', { exact: true })).toBeVisible() 428 + 429 + // match only failing files when fail filter applied 430 + await page.getByPlaceholder('Search...').fill('') 431 + await page.getByText(/^Fail$/, { exact: true }).click() 432 + await page.getByText('FAIL (2)').click() 433 + await expect(page.getByTestId('results-panel').getByText('error.test.ts', { exact: true })).toBeVisible() 434 + await expect(page.getByTestId('results-panel').getByText('sample.test.ts', { exact: true })).toBeHidden() 435 + 436 + // match only pass files when fail filter applied 437 + await page.getByPlaceholder('Search...').fill('console') 438 + await page.getByText(/^Fail$/, { exact: true }).click() 439 + await page.locator('span').filter({ hasText: /^Pass$/ }).click() 440 + await page.getByText('PASS (1)').click() 441 + await expect(page.getByTestId('results-panel').getByText('console.test.ts', { exact: true })).toBeVisible() 442 + await expect(page.getByTestId('results-panel').getByText('sample.test.ts', { exact: true })).toBeHidden() 443 + 444 + // html entities in task names are escaped 445 + await page.locator('span').filter({ hasText: /^Pass$/ }).click() 446 + await page.getByPlaceholder('Search...').fill('<MyComponent />') 447 + // for some reason, the tree is collapsed by default: we need to click on the nav buttons to expand it 448 + await page.getByTestId('collapse-all').click() 449 + await page.getByTestId('expand-all').click() 450 + await expect(page.getByText('<MyComponent />')).toBeVisible() 451 + await expect(page.getByTestId('results-panel').getByText('task-name.test.ts', { exact: true })).toBeVisible() 452 + 453 + // html entities in task names are escaped 454 + await page.getByPlaceholder('Search...').fill('<>\'"') 455 + await expect(page.getByText('<>\'"')).toBeVisible() 456 + await expect(page.getByTestId('results-panel').getByText('task-name.test.ts', { exact: true })).toBeVisible() 457 + 458 + // pass files with special chars 459 + await page.getByPlaceholder('Search...').fill('char () - Square root of nine (9)') 460 + const testItem = getExplorerItem(page, 'char () - Square root of nine (9)') 461 + await expect(testItem).toBeVisible() 462 + if (options.mode === 'ui') { 463 + await testItem.hover() 464 + await testItem.getByLabel('Run current test').click() 465 + await expect(page.getByText('The test has passed without any errors')).toBeVisible() 466 + } 467 + } 468 + 469 + async function testCrossOriginAccess(page: Page, pageUrl: string) { 470 + await page.route('https://example.com/**', (route) => { 471 + return route.fulfill({ 472 + status: 200, 473 + contentType: 'text/html', 474 + body: '<html><body><h1>Faked Cross Origin Site</h1></body></html>', 475 + }) 476 + }) 477 + await page.goto('https://example.com/', { timeout: 5000 }) 478 + 479 + // request html 480 + const htmlResult = await page.evaluate(async (pageUrl) => { 481 + try { 482 + const res = await fetch(pageUrl) 483 + return res.status 484 + } 485 + catch (e) { 486 + return e instanceof Error ? e.message : e 487 + } 488 + }, pageUrl) 489 + expect(htmlResult).toBe('Failed to fetch') 490 + 491 + // request websocket 492 + const wsResult = await page.evaluate(async (pageUrl) => { 493 + const ws = new WebSocket(new URL('/__vitest_api__', pageUrl)) 494 + return new Promise((resolve) => { 495 + ws.addEventListener('open', () => { 496 + resolve('open') 497 + }) 498 + ws.addEventListener('error', () => { 499 + resolve('error') 500 + }) 501 + }) 502 + }, pageUrl) 503 + expect(wsResult).toBe('error') 504 + } 505 + 506 + async function testWriteFile(page: Page, options: { enabled: boolean }) { 507 + await getExplorerItem(page, 'add').click() 508 + const codeTabButton = page.getByTestId('btn-code') 509 + await expect(codeTabButton).toHaveText('Code') 510 + await codeTabButton.click() 511 + const editor = page.getByTestId('editor') 512 + await expect(editor).toContainText('expect(1 + 1).toEqual(2)') 513 + await page.keyboard.type('\n// edited \n') 514 + if (options.enabled) { 515 + await expect(editor).toContainText('// edited') 516 + } 517 + else { 518 + await expect(editor).not.toContainText('// edited') 519 + } 520 + } 521 + 522 + async function testExecute(page: Page, options: { mode: 'ui' | 'ui-disallow' | 'static' }) { 523 + if (options.mode === 'ui') { 524 + await expect(page.getByTestId('btn-run-all')).toBeEnabled() 525 + 526 + const item = getExplorerItem(page, 'add') 527 + await item.hover() 528 + await expect(item.getByTestId('btn-run-test')).toBeEnabled() 529 + 530 + await page.getByPlaceholder('Search...').fill('snapshot') 531 + const snapshotItem = getExplorerItem(page, 'snapshot.test.ts') 532 + await snapshotItem.hover() 533 + await expect(snapshotItem.getByTestId('btn-fix-snapshot')).toBeVisible() 534 + } 535 + if (options.mode === 'ui-disallow') { 536 + await expect(page.getByTestId('btn-run-all')).toBeDisabled() 537 + 538 + const item = getExplorerItem(page, 'add') 539 + await item.hover() 540 + await expect(item.getByTestId('btn-run-test')).toBeDisabled() 541 + 542 + await page.getByPlaceholder('Search...').fill('snapshot') 543 + const snapshotItem = getExplorerItem(page, 'snapshot.test.ts') 544 + await snapshotItem.hover() 545 + await expect(snapshotItem.getByTestId('btn-fix-snapshot')).not.toBeVisible() 546 + } 547 + if (options.mode === 'static') { 548 + await expect(page.getByTestId('btn-run-all')).not.toBeVisible() 549 + 550 + const item = getExplorerItem(page, 'add') 551 + await item.hover() 552 + await expect(item.getByTestId('btn-run-test')).not.toBeVisible() 553 + 554 + await page.getByPlaceholder('Search...').fill('snapshot') 555 + const snapshotItem = getExplorerItem(page, 'snapshot.test.ts') 556 + await snapshotItem.hover() 557 + await expect(snapshotItem.getByTestId('btn-fix-snapshot')).not.toBeVisible() 558 + } 389 559 } 390 560 391 561 test.describe('standalone', () => { 392 562 let vitest: Vitest | undefined 563 + let pageUrl: string 393 564 394 565 test.beforeAll(async () => { 395 - // silence Vitest logs 396 - const stdout = new Writable({ write: (_, __, callback) => callback() }) 397 - const stderr = new Writable({ write: (_, __, callback) => callback() }) 398 - vitest = await startVitest('test', [], { 566 + const server = await startVitestUi({ 567 + root: './fixtures/main', 399 568 watch: true, 400 569 ui: true, 401 570 standalone: true, 402 571 open: false, 403 - api: { port }, 404 572 reporters: [], 405 - }, {}, { 406 - stdout, 407 - stderr, 408 573 }) 409 - expect(vitest).toBeDefined() 574 + vitest = server.vitest 575 + pageUrl = `${server.url}/__vitest__/` 410 576 }) 411 577 412 578 test.afterAll(async () => { ··· 417 583 await page.goto(pageUrl) 418 584 419 585 // initially no stats 420 - await expect(page.locator('[aria-labelledby=tests]')).toContainText('0 Pass 0 Fail 0 Total') 586 + await assertTestCounts(page, { pass: 0, fail: 0 }) 421 587 422 588 // run single file 423 - await page.getByText('fixtures/sample.test.ts').hover() 589 + await getExplorerItem(page, 'sample.test.ts').hover() 424 590 await page.getByRole('button', { name: 'Run current file' }).click() 425 591 426 592 // check results 427 - await page.getByText('PASS (1)').click() 593 + await page.getByRole('button', { name: 'Show dashboard' }).click() 594 + await assertTestCounts(page, { pass: 2, fail: 0 }) 428 595 expect(vitest?.state.getFiles().map(f => [f.name, f.result?.state])).toEqual([ 429 - ['fixtures/sample.test.ts', 'pass'], 596 + ['sample.test.ts', 'pass'], 430 597 ]) 598 + }) 599 + }) 600 + 601 + test.describe('security', () => { 602 + let vitest: Vitest | undefined 603 + let pageUrl: string 604 + 605 + test.beforeAll(async () => { 606 + const server = await startVitestUi({ 607 + root: './fixtures/main', 608 + watch: true, 609 + ui: true, 610 + open: false, 611 + api: { 612 + allowExec: false, 613 + allowWrite: false, 614 + }, 615 + reporters: [], 616 + }) 617 + vitest = server.vitest 618 + pageUrl = `${server.url}/__vitest__/` 619 + }) 620 + 621 + test.afterAll(async () => { 622 + await vitest?.close() 623 + }) 624 + 625 + test('cannot write file', async ({ page }) => { 626 + await page.goto(pageUrl) 627 + await testWriteFile(page, { enabled: false }) 628 + }) 629 + 630 + test('cannot execute', async ({ page }) => { 631 + await page.goto(pageUrl) 632 + await testExecute(page, { mode: 'ui-disallow' }) 431 633 }) 432 634 })
-4
test/ui/fixtures-trace/assets/trace-pixel.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> 2 - <rect width="24" height="24" fill="#2563eb"/> 3 - <circle cx="12" cy="12" r="6" fill="#dc2626"/> 4 - </svg>
-4
test/ui/fixtures-trace/assets/trace-style.css
··· 1 - .trace-linked-css { 2 - color: rgb(50, 100, 255); 3 - font-weight: 700; 4 - }
+49
test/ui/fixtures/main/vitest.config.ts
··· 1 + import path, { resolve } from "node:path"; 2 + import { playwright } from "@vitest/browser-playwright"; 3 + import { defineConfig } from "vitest/config"; 4 + import type { BrowserCommand } from "vitest/node"; 5 + import fs from "node:fs" 6 + 7 + const rmCommand: BrowserCommand<[filepath: string]> = async (ctx, filePath) => { 8 + const resolved = resolve(ctx.project.config.root, filePath) 9 + fs.rmSync(resolved, { recursive: true, force: true }) 10 + } 11 + 12 + export default defineConfig({ 13 + test: { 14 + coverage: { 15 + reportOnFailure: true, 16 + }, 17 + tags: [{ name: "db" }, { name: "flaky" }], 18 + projects: [ 19 + { 20 + extends: true, 21 + test: { 22 + name: "node", 23 + root: path.join(import.meta.dirname, "node"), 24 + // TODO: https://github.com/vitest-dev/vitest/issues/10326 25 + attachmentsDir: path.join(import.meta.dirname, ".vitest/attachments"), 26 + environment: "happy-dom", 27 + }, 28 + }, 29 + { 30 + extends: true, 31 + test: { 32 + name: "browser", 33 + root: path.join(import.meta.dirname, "browser"), 34 + attachmentsDir: path.join(import.meta.dirname, ".vitest/attachments"), 35 + browser: { 36 + enabled: true, 37 + headless: true, 38 + provider: playwright(), 39 + instances: [{ browser: "chromium" }], 40 + screenshotFailures: false, 41 + commands: { 42 + rm: rmCommand, 43 + } 44 + }, 45 + }, 46 + }, 47 + ], 48 + }, 49 + });
+18
test/ui/fixtures/trace-stream/basic.test.ts
··· 1 + import { test } from 'vitest' 2 + import { page } from 'vitest/browser' 3 + import { waitForGate } from './helper' 4 + 5 + test('simple', async () => { 6 + document.body.innerHTML = '<button>A</button>' 7 + await page.getByRole('button', { name: 'A' }).mark('render-a') 8 + 9 + await waitForGate('b') 10 + 11 + document.body.innerHTML += '<button>B</button>' 12 + await page.getByRole('button', { name: 'B' }).mark('render-b') 13 + 14 + await waitForGate('c') 15 + 16 + document.body.innerHTML += '<button>C</button>' 17 + await page.getByRole('button', { name: 'C' }).mark('render-c') 18 + })
+21
test/ui/fixtures/trace-stream/helper.ts
··· 1 + import { vi } from 'vitest' 2 + import { commands } from 'vitest/browser' 3 + 4 + // use file system command to communicate with e2e 5 + // to precisely control the timing of incremental trace update 6 + export async function waitForGate(name: string) { 7 + if (!(import.meta as any).env.TEST_GATE_FILE) { 8 + await new Promise(r => setTimeout(r, 2000)) 9 + return 10 + } 11 + const gatePath = `./node_modules/.vitest-e2e/${name}.txt` 12 + await vi.waitUntil(async () => { 13 + try { 14 + const content = await commands.readFile(gatePath) 15 + return content.includes('open') 16 + } 17 + catch { 18 + return false 19 + } 20 + }, { timeout: 10000 }) 21 + }
+29
test/ui/fixtures/trace-stream/range.test.ts
··· 1 + import { expect, test } from 'vitest' 2 + import { page } from 'vitest/browser' 3 + import { waitForGate } from './helper' 4 + 5 + test('expect', async () => { 6 + document.body.innerHTML = '<button>A</button>' 7 + await Promise.all([ 8 + (async () => { 9 + await expect 10 + .element(page.getByRole('button', { name: 'B' }), { timeout: 10000 }) 11 + .toBeVisible() 12 + })(), 13 + (async () => { 14 + await waitForGate('expect-b') 15 + document.body.innerHTML = '<button>B</button>' 16 + })(), 17 + ]) 18 + await Promise.all([ 19 + (async () => { 20 + await expect 21 + .element(page.getByRole('button').filter({ hasText: 'C' }), { timeout: 10000 }) 22 + .toHaveAttribute('data-testid', 'c') 23 + })(), 24 + (async () => { 25 + await waitForGate('expect-c') 26 + document.body.innerHTML = '<button data-testid="c">C</button>' 27 + })(), 28 + ]) 29 + })
+21
test/ui/fixtures/trace-stream/vitest.config.ts
··· 1 + import { playwright } from '@vitest/browser-playwright' 2 + import { defineConfig } from 'vitest/config' 3 + 4 + export default defineConfig({ 5 + test: { 6 + ui: true, 7 + browser: { 8 + enabled: true, 9 + provider: playwright(), 10 + instances: [ 11 + { browser: 'chromium' }, 12 + ], 13 + headless: true, 14 + ui: false, 15 + traceView: { 16 + enabled: true, 17 + }, 18 + screenshotFailures: false, 19 + }, 20 + }, 21 + })
+84
test/ui/fixtures/trace/basic.test.ts
··· 1 + import { expect, onTestFinished, test } from 'vitest' 2 + import { commands, page } from 'vitest/browser' 3 + 4 + // tests for full snapshot/replay integration. 5 + // partly extracted from artifact metadata tests in 6 + // test/browser/fixtures/trace/*.test.ts 7 + 8 + test('simple', async () => { 9 + document.body.innerHTML = '<button>Simple</button>' 10 + await page.getByRole('button').mark('Render simple') 11 + }) 12 + 13 + test('switch-target', async () => { 14 + document.body.innerHTML = '<button>Switch Target</button>' 15 + await page.getByRole('button').mark('Render switch target') 16 + }) 17 + 18 + test('pseudo-state', async () => { 19 + document.body.innerHTML = ` 20 + <style> 21 + .test-target { 22 + background: rgb(255, 200, 200); 23 + } 24 + .test-hover:hover, 25 + .test-focus:focus, 26 + .test-focus-within:focus-within, 27 + .test-active:active, 28 + .test-focus-visible:focus-visible, 29 + .test-none 30 + { 31 + background: rgb(253, 224, 71); 32 + } 33 + </style> 34 + <button class="test-target test-hover">Test hover 1</button> 35 + <hr /> 36 + <button class="test-target test-hover">Test hover 2</button> 37 + <hr /> 38 + <label style="display: block; padding: 8px;"> 39 + Test focus 40 + <input class="test-target test-focus" placeholder="focus-placeholder"> 41 + </label> 42 + <hr /> 43 + <label class="test-target test-focus-within" style="display: block; padding: 8px;"> 44 + Test focus-within 45 + <input placeholder="focus-within-placeholder"> 46 + </label> 47 + <hr /> 48 + <button class="test-target test-active">Test active</button> 49 + <hr /> 50 + <label style="display: block; padding: 8px;"> 51 + Test focus-visible 52 + <input class="test-target test-focus-visible" placeholder="focus-visible-placeholder"> 53 + </label> 54 + ` 55 + await page.getByRole('button', { name: 'Test hover 1' }).hover() 56 + await page.getByRole('button', { name: 'Test hover 2' }).click() 57 + await page.getByPlaceholder('focus-placeholder').click() 58 + await page.getByPlaceholder('focus-placeholder').fill('focus-done') 59 + await page.getByPlaceholder('focus-within-placeholder').fill('focus-within-done') 60 + await (commands as any).mousedown('.test-active') 61 + await page.getByRole('button', { name: 'Test active' }).mark('Test active') 62 + await page.getByPlaceholder('focus-visible-placeholder').fill('focus-visible-done') 63 + }) 64 + 65 + test('css-link', async () => { 66 + const link = document.createElement('link') 67 + link.rel = 'stylesheet' 68 + link.href = '/assets/trace-style.css' 69 + document.head.append(link) 70 + onTestFinished(() => { 71 + link.remove() 72 + }) 73 + document.body.innerHTML = '<button class="trace-linked-css">Linked CSS</button>' 74 + await expect.element(page.getByRole('button', { name: 'Linked CSS' })) 75 + .toHaveStyle({ color: 'rgb(50, 100, 255)' }) 76 + }) 77 + 78 + test('image', async () => { 79 + document.body.innerHTML = '<img src="/assets/trace-pixel.svg" alt="local trace asset" width="24" height="24">' 80 + await expect.element(page.getByAltText('local trace asset')) 81 + .not 82 + .toHaveProperty('naturalWidth', 0) 83 + await page.getByAltText('local trace asset').mark('Render image') 84 + })
+40
test/ui/fixtures/trace/retry.test.ts
··· 1 + import type { TestContext } from 'vitest' 2 + import { test, vi } from 'vitest' 3 + import { page } from 'vitest/browser' 4 + 5 + const renderContext = vi.defineHelper(async (context: TestContext) => { 6 + const result = context.task.result 7 + const content = ` 8 + <ul> 9 + <li>retryCount: ${result?.retryCount ?? '(none)'}</li> 10 + <li>repeatCount: ${result?.repeatCount ?? '(none)'}</li> 11 + </ul> 12 + ` 13 + document.body.innerHTML = content 14 + await page.getByRole('list').mark(`renderHelper`) 15 + }) 16 + 17 + test('repeated test', { repeats: 2 }, async ({ task }) => { 18 + await renderContext(task.context) 19 + }) 20 + 21 + test('retried test', { retry: 2 }, async ({ task }) => { 22 + await renderContext(task.context) 23 + if (task.result?.retryCount !== 2) { 24 + throw new Error(`failed test at retry count ${task.result?.retryCount}`) 25 + } 26 + }) 27 + 28 + test('repeated retried tests', { repeats: 2, retry: 2 }, async ({ task }) => { 29 + await renderContext(task.context) 30 + if (task.result?.retryCount !== 2) { 31 + throw new Error(`failed test at retry count ${task.result?.retryCount}`) 32 + } 33 + }) 34 + 35 + test('repeated test retried on later repeat', { repeats: 2, retry: 2 }, async ({ task }) => { 36 + await renderContext(task.context) 37 + if (task.result?.repeatCount === 1 && task.result.retryCount !== 1) { 38 + throw new Error(`failed test at retry count ${task.result?.retryCount}`) 39 + } 40 + })
+34
test/ui/fixtures/trace/scroll.test.ts
··· 1 + import { test } from 'vitest' 2 + import { page } from 'vitest/browser' 3 + 4 + test('scroll', async () => { 5 + const [width, height] = [300, 300] 6 + await page.viewport(width, height) 7 + document.body.innerHTML = ` 8 + <style> 9 + html, 10 + body { 11 + margin: 0; 12 + height: 100vh; 13 + width: 100vw; 14 + background: linear-gradient(-45deg, purple, yellow); 15 + } 16 + .marker { 17 + position: absolute; 18 + width: 100px; 19 + background: white; 20 + border: 1px solid black; 21 + text-align: center; 22 + } 23 + </style> 24 + <main style="height: 600px; width: 600px;"> 25 + <div class="marker" style="top: 0; left: 0;">(0, 0)</div> 26 + <div class="marker" style="top: 0; left: 300px;">(300, 0)</div> 27 + <div class="marker" style="top: 300px; left: 0;">(0, 300)</div> 28 + <div class="marker" style="top: 300px; left: 300px;">(300, 300)</div> 29 + </main> 30 + ` 31 + // scroll to make (300, 300) visible and (0, 0) not visible 32 + window.scrollTo(250, 200) 33 + await page.getByRole('button').mark('Render scroll') 34 + })
+42
test/ui/fixtures/trace/viewport.test.ts
··· 1 + import { test } from 'vitest' 2 + import { page } from 'vitest/browser' 3 + 4 + test('viewport', async () => { 5 + const [width, height] = [500, 300] 6 + await page.viewport(width, height) 7 + document.body.innerHTML = ` 8 + <style> 9 + html, 10 + body { 11 + margin: 0; 12 + height: 100vh; 13 + background: linear-gradient(-45deg, blue, orange); 14 + } 15 + 16 + .viewport-pass { 17 + display: none; 18 + } 19 + 20 + .viewport-fail { 21 + display: block; 22 + } 23 + 24 + @media (width: ${width}px) and (height: ${height}px) { 25 + .viewport-pass { 26 + display: block; 27 + } 28 + 29 + .viewport-fail { 30 + display: none; 31 + } 32 + } 33 + </style> 34 + <div class="viewport-pass"> 35 + PASS: Viewport is ${width}x${height} 36 + </div> 37 + <div class="viewport-fail"> 38 + FAIL: Viewport is not ${width}x${height} 39 + </div> 40 + ` 41 + await page.mark('Render viewport') 42 + })
+34
test/ui/fixtures/trace/vitest.config.ts
··· 1 + import type { BrowserCommand } from 'vitest/node' 2 + import { playwright } from '@vitest/browser-playwright' 3 + import { defineConfig } from 'vitest/config' 4 + 5 + // mousedown through custom command 6 + // https://github.com/vitest-dev/vitest/issues/8190 7 + const mousedownCommand: BrowserCommand<[selector: string]> = async (ctx, selector) => { 8 + await ctx.iframe.locator(selector).hover() 9 + await ctx.page.mouse.down() 10 + } 11 + 12 + export default defineConfig({ 13 + test: { 14 + ui: true, 15 + browser: { 16 + enabled: true, 17 + provider: playwright(), 18 + instances: [ 19 + { browser: 'chromium' }, 20 + ], 21 + headless: true, 22 + ui: false, 23 + traceView: { 24 + enabled: true, 25 + // enabled only on html reporter e2e 26 + // inlineImages: true, 27 + }, 28 + screenshotFailures: false, 29 + commands: { 30 + mousedown: mousedownCommand, 31 + }, 32 + }, 33 + }, 34 + })
+10
test/ui/fixtures/main/browser/visual-regression.test.ts
··· 1 + import { test } from 'vitest' 2 + import { server } from 'vitest/browser' 3 + 4 + test('visual regression test', async ({ expect }) => { 5 + // reset screenshots to ensure consistent assertion results with new screenshot 6 + await (server.commands as any).rm(`__screenshots__`) 7 + await expect(expect(document.body).toMatchScreenshot()).rejects.toThrow( 8 + 'No existing reference screenshot found', 9 + ) 10 + })
+37
test/ui/fixtures/main/node/annotated.test.ts
··· 1 + import { test } from 'vitest' 2 + 3 + test('annotated test', async ({ annotate }) => { 4 + await annotate('hello world') 5 + await annotate('second annotation') 6 + }) 7 + 8 + test('annotated typed test', async ({ annotate }) => { 9 + await annotate('beware!', 'warning') 10 + }) 11 + 12 + test('annotated file test', async ({ annotate }) => { 13 + await annotate('file annotation', { 14 + path: './example.txt' 15 + }) 16 + }) 17 + 18 + test('annotated image test', async ({ annotate }) => { 19 + await annotate('image annotation', { 20 + path: './cute-puppy.jpg' 21 + }) 22 + }) 23 + 24 + test('annotated with body base64', async ({ annotate }) => { 25 + await annotate('body base64 annotation', { 26 + contentType: 'text/markdown', 27 + body: btoa('Hello base64 **markdown**'), 28 + }) 29 + }) 30 + 31 + test('annotated with body utf-8', async ({ annotate }) => { 32 + await annotate('body utf-8 annotation', { 33 + contentType: 'text/markdown', 34 + body: 'Hello utf-8 **markdown**', 35 + bodyEncoding: 'utf-8', 36 + }) 37 + })
+72
test/ui/fixtures/main/node/console.test.ts
··· 1 + /* eslint-disable no-console */ 2 + 3 + import { afterAll, beforeAll, it, describe, expect } from "vitest"; 4 + import { prettyDOM } from "@testing-library/dom"; 5 + 6 + // https://github.com/vitest-dev/vitest/issues/2765 7 + it('regexp', () => { 8 + console.log(/(?<char>\w)/) 9 + }) 10 + 11 + // https://github.com/vitest-dev/vitest/issues/3934 12 + it('html-raw', async () => { 13 + console.log(` 14 + <form> 15 + <label for="email">Email Address</label> 16 + <input name="email" /> 17 + <button>Submit</button> 18 + </form> 19 + `); 20 + }) 21 + 22 + // https://github.com/vitest-dev/vitest/issues/1279 23 + it('html-pretty', () => { 24 + const div = document.createElement("div"); 25 + div.innerHTML = ` 26 + <form> 27 + <label for="email">Email Address</label> 28 + <input name="email" /> 29 + <button>Submit</button> 30 + </form> 31 + `.replaceAll(/\n */gm, ""); // strip new lines 32 + console.log(prettyDOM(div)) 33 + }) 34 + 35 + 36 + beforeAll(() => { 37 + console.log('beforeAll') 38 + console.error('beforeAll') 39 + }) 40 + 41 + afterAll(() => { 42 + console.log('afterAll') 43 + console.error('afterAll') 44 + }) 45 + 46 + describe('suite', () => { 47 + beforeAll(() => { 48 + console.log('beforeAll') 49 + console.error('beforeAll') 50 + }) 51 + 52 + afterAll(() => { 53 + console.log('afterAll') 54 + console.error('afterAll') 55 + }) 56 + 57 + describe('nested suite', () => { 58 + beforeAll(() => { 59 + console.log('beforeAll') 60 + console.error('beforeAll') 61 + }) 62 + 63 + afterAll(() => { 64 + console.log('afterAll') 65 + console.error('afterAll') 66 + }) 67 + 68 + it('test', () => { 69 + expect(true).toBe(true) 70 + }) 71 + }) 72 + })
+6
test/ui/fixtures/main/node/coverage.test.ts
··· 1 + import { expect, it } from 'vitest' 2 + import { multiply } from './coverage' 3 + 4 + it(multiply, () => { 5 + expect(multiply(2, 3)).toEqual(6) 6 + })
+3
test/ui/fixtures/main/node/coverage.ts
··· 1 + export function multiply(n: number, m: number) { 2 + return n * m; 3 + }
test/ui/fixtures/main/node/cute-puppy.jpg

This is a binary file and will not be displayed.

+14
test/ui/fixtures/main/node/error.test.ts
··· 1 + import { expect, it } from "vitest" 2 + 3 + // https://github.com/vitest-dev/vitest/issues/5321 4 + it('escape html in error diff', () => { 5 + expect('<style>* {border: 2px solid green};</style>').toBe("") 6 + }) 7 + 8 + it('colored error message', () => { 9 + const blue = '\x1B[34m' 10 + const reset = '\x1B[0m' 11 + const message = `${blue}this-is-blue${reset}` 12 + const error = new Error(message) 13 + throw error 14 + })
+1
test/ui/fixtures/main/node/example.txt
··· 1 + hello world
+17
test/ui/fixtures/main/node/sample.test.ts
··· 1 + import { expect, it } from 'vitest' 2 + 3 + it('add', () => { 4 + // eslint-disable-next-line no-console 5 + console.log('log test') 6 + setTimeout(() => { 7 + throw new Error('error') 8 + }) 9 + setTimeout(() => { 10 + throw 1 11 + }) 12 + expect(1 + 1).toEqual(2) 13 + }) 14 + 15 + it('has tags', { tags: ['db', 'flaky'] }, () => { 16 + // ... 17 + })
+5
test/ui/fixtures/main/node/snapshot.test.ts
··· 1 + import { expect, test } from 'vitest'; 2 + 3 + test('wrong snapshot', () => { 4 + expect(1).toMatchInlineSnapshot(`2`) 5 + })
+13
test/ui/fixtures/main/node/task-name.test.ts
··· 1 + import { it, expect} from "vitest" 2 + 3 + it('<MyComponent />', () => { 4 + expect(true).toBe(true) 5 + }) 6 + 7 + it('<>\'"', () => { 8 + expect(true).toBe(true) 9 + }) 10 + 11 + it('char () - Square root of nine (9)', () => { 12 + expect(Math.sqrt(9)).toBe(3); 13 + });
+4
test/ui/fixtures/trace/assets/trace-pixel.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> 2 + <rect width="24" height="24" fill="#2563eb"/> 3 + <circle cx="12" cy="12" r="6" fill="#dc2626"/> 4 + </svg>
+4
test/ui/fixtures/trace/assets/trace-style.css
··· 1 + .trace-linked-css { 2 + color: rgb(50, 100, 255); 3 + font-weight: 700; 4 + }