Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335import { readdirSync, rmSync } from 'node:fs'import path from 'node:path'import { stripVTControlCharacters } from 'node:util'import { resolve } from 'pathe'import { afterEach, describe, expect, test } from 'vitest'import * as yauzl from 'yauzl'import { buildTestProjectTree } from '../../test-utils'import { instances, provider, runBrowserTests } from './utils'
const tracesFolder = resolve(import.meta.dirname, '../fixtures/trace-mark/__traces__')const basicTestTracesFolder = resolve(tracesFolder, 'basic.test.ts')
describe.runIf(provider.name === 'playwright')('playwright trace marks', () => { afterEach(() => { rmSync(tracesFolder, { recursive: true, force: true }) })
test('vitest mark is present in zipped trace events', async () => { const { results, ctx } = await runBrowserTests({ root: './fixtures/trace-mark', browser: { trace: { mode: 'on', screenshots: false, // makes it lighter }, }, }) const projectTree = buildTestProjectTree(results, (testCase) => { const result = testCase.result() return result.state === 'failed' ? result.errors.map((e) => stripVTControlCharacters(e.message)) : result.state }) expect(Object.keys(projectTree).sort()).toEqual(instances.map((i) => i.browser).sort())
for (const [name, tree] of Object.entries(projectTree)) { expect.soft(tree).toMatchInlineSnapshot(` { "basic.test.ts": { "click": "passed", "expect.element fail": [ "expect(element).toHaveTextContent()
Expected element to have text content: World Received: Hello", ], "expect.element pass": "passed", "failure": [ "Test failure", ], "helper": "passed", "locator.mark": "passed", "mark group": "passed", "page.mark": "passed", "stack": "passed", }, } `)
const traceFiles = readdirSync(basicTestTracesFolder) .filter((file) => file.startsWith(`${name}-`) && file.endsWith('.trace.zip')) .sort() expect .soft(traceFiles) .toEqual([ expect.stringContaining('click'), expect.stringContaining('expect-element-fail'), expect.stringContaining('expect-element-pass'), expect.stringContaining('failure'), expect.stringContaining('helper'), expect.stringContaining('locator-mark'), expect.stringContaining('mark-group'), expect.stringContaining('page-mark'), expect.stringContaining('stack'), ])
function formatStack(event: any) { return event.stack ?.map( (frame: any) => `${path.relative(ctx.config.root, frame.file)}:${frame.line}:${frame.column}`, ) .join('\n') }
for (const traceFile of traceFiles) { const zipPath = resolve(basicTestTracesFolder, traceFile) const parsed = await readTraceZip(zipPath) const events = parsed.events.filter((event) => event.type === 'before')
if (traceFile.includes('locator-mark')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ method: 'tracingGroup', title: 'button rendered - locator', }), expect.objectContaining({ method: 'expect', params: expect.objectContaining({ selector: expect.stringContaining(`internal:describe="getByRole('button')`), }), }), ]), ) const markerEvent = events.find((e) => e.title === 'button rendered - locator') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:10:34"`) }
if (traceFile.includes('page-mark') && !traceFile.includes('custom-stack')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ method: 'tracingGroup', title: 'button rendered - page', }), expect.objectContaining({ method: 'evaluateExpression', }), ]), ) const markerEvent = events.find((e) => e.title === 'button rendered - page') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:15:14"`) }
if (traceFile.includes('expect-element-pass')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ method: 'tracingGroup', title: 'toHaveTextContent', }), expect.objectContaining({ method: 'expect', params: expect.objectContaining({ selector: expect.stringContaining(`internal:describe="getByRole('button')`), }), }), ]), ) const markerEvent = events.find((e) => e.title === 'toHaveTextContent') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:20:16"`) }
if (traceFile.includes('expect-element-fail')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ method: 'tracingGroup', title: 'button rendered', }), expect.objectContaining({ method: 'tracingGroup', title: 'toHaveTextContent [ERROR]', }), expect.objectContaining({ method: 'expect', params: expect.objectContaining({ selector: expect.stringContaining(`internal:describe="getByRole('button')`), }), }), ]), ) const markerEvent = events.find((e) => e.title === 'toHaveTextContent [ERROR]') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:26:16"`) }
if (traceFile.includes('failure')) { const markerEvent = events.find((e) => e.title === 'onAfterRetryTask [fail]') const formattedFrame = formatStack(markerEvent) if (name === 'webkit') { expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:31:13"`) } else { expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:31:9"`) } }
if (traceFile.includes('click')) { expect(events).toEqual( expect.arrayContaining([ // vitest command group (with source) expect.objectContaining({ method: 'tracingGroup', title: 'vitest:click', }), // playwright action (without source) expect.objectContaining({ method: 'click', params: expect.objectContaining({ selector: expect.stringContaining(`internal:describe="getByRole('button')`), }), }), ]), ) const markerEvent = events.find((e) => e.title === 'vitest:click') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:36:34"`) }
if (traceFile.includes('helper')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'render helper', }), ]), ) const markerEvent = events.find((e) => e.title === 'render helper') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:45:9"`) }
if (traceFile.includes('stack')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'button rendered - stack', }), ]), ) const markerEvent = events.find((e) => e.title === 'button rendered - stack') const formattedFrame = formatStack(markerEvent) if (name === 'webkit') { expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:50:21"`) } else { expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:50:17"`) } }
if (traceFile.includes('mark-group')) { expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'render group', }), ]), ) const markerEvent = events.find((e) => e.title === 'render group') const formattedFrame = formatStack(markerEvent) expect(formattedFrame).toMatchInlineSnapshot(`"basic.test.ts:55:14"`) } } } })})
async function readTraceZip(zipPath: string): Promise<{ entries: string[]; events: any[] }> { const zipFile = new ZipFile(zipPath) try { const entries = await zipFile.entries() const traceText = (await zipFile.read('trace.trace')).toString('utf-8') const events = traceText .split('\n') .filter(Boolean) .map((line) => { return JSON.parse(line) }) return { entries, events } } finally { zipFile.close() }}
// https://github.com/microsoft/playwright/blob/cd36dab6ecc7f4b3adeec333e55f9ac03711a9b1/packages/playwright-core/src/server/utils/zipFile.ts#L21class ZipFile { private readonly fileName: string private zipFile?: yauzl.ZipFile private readonly openedPromise: Promise<void> private readonly entriesMap = new Map<string, yauzl.Entry>()
constructor(fileName: string) { this.fileName = fileName this.openedPromise = this.open() }
private async open(): Promise<void> { this.zipFile = await new Promise<yauzl.ZipFile>((resolve, reject) => { yauzl.open(this.fileName, { lazyEntries: true, autoClose: false }, (error, zipFile) => { if (error || !zipFile) { reject(error || new Error(`Cannot open zip: ${this.fileName}`)) return } resolve(zipFile) }) })
await new Promise<void>((resolve, reject) => { this.zipFile!.readEntry() this.zipFile!.on('entry', (entry) => { this.entriesMap.set(entry.fileName, entry) this.zipFile!.readEntry() }) this.zipFile!.on('end', resolve) this.zipFile!.on('error', reject) }) }
async entries(): Promise<string[]> { await this.openedPromise return [...this.entriesMap.keys()] }
async read(entryPath: string): Promise<Buffer> { await this.openedPromise const entry = this.entriesMap.get(entryPath) if (!entry || !this.zipFile) { throw new Error(`${entryPath} not found in file ${this.fileName}`) }
return await new Promise((resolve, reject) => { this.zipFile!.openReadStream(entry, (error, stream) => { if (error || !stream) { reject(error || new Error(`Cannot read ${entryPath} from file ${this.fileName}`)) return }
const buffers: Buffer[] = [] stream.on('data', (data) => buffers.push(data)) stream.on('error', reject) stream.on('end', () => resolve(Buffer.concat(buffers))) }) }) }
close(): void { this.zipFile?.close() }}