Skip to content

Commit aa5140e

Browse files
authored
feat(browser): add context.mark for custom command tracing (#10329)
1 parent 78c1169 commit aa5140e

11 files changed

Lines changed: 111 additions & 9 deletions

File tree

docs/api/browser/commands.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,25 @@ declare module 'vitest/browser' {
124124
Custom functions will override built-in ones if they have the same name.
125125
:::
126126

127+
### Recording trace markers
128+
129+
Custom commands can record [trace markers](/api/browser/context#mark) for the test that triggered them through `context.mark`. This is the server-side equivalent of `page.mark` and helps annotate the [trace view](/guide/browser/trace-view) with custom actions performed inside a command.
130+
131+
```ts
132+
import type { BrowserCommand } from 'vitest/node'
133+
134+
export const uploadFixture: BrowserCommand<[name: string]> = async (
135+
context,
136+
name,
137+
) => {
138+
await context.mark(`upload start: ${name}`, { kind: 'action' })
139+
// ... do server-side work
140+
await context.mark(`upload done: ${name}`, { kind: 'action' })
141+
}
142+
```
143+
144+
`context.mark` is a no-op when browser tracing is not enabled or no test is currently running in the session. Unlike `page.mark`, it does not accept a callback form.
145+
127146
### Custom `playwright` commands
128147

129148
Vitest exposes several `playwright` specific properties on the command context.

docs/api/browser/context.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ await page.mark('submit flow', async () => {
158158

159159
::: tip
160160
This method is useful only when [`browser.trace`](/config/browser/trace) is enabled.
161+
162+
A server-side equivalent is available on the [`BrowserCommandContext`](/api/browser/commands#recording-trace-markers) so [custom commands](/api/browser/commands#custom-commands) can record markers attributed to the test that triggered them.
161163
:::
162164

163165
### frameLocator

packages/browser/src/client/client.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ModuleMocker } from '@vitest/mocker/browser'
22
import type { CancelReason } from '@vitest/runner'
33
import type { BirpcReturn } from 'birpc'
4+
import type { MarkOptions } from 'vitest/browser'
45
import type { WebSocketBrowserEvents, WebSocketBrowserHandlers } from '../types'
56
import type { IframeOrchestrator } from './orchestrator'
67
import { createBirpc } from 'birpc'
@@ -26,6 +27,12 @@ export function onCancel(callback: (reason: CancelReason) => void): void {
2627
onCancelCallbacks.push(callback)
2728
}
2829

30+
let pageMarkHandler: ((name: string, options?: MarkOptions) => Promise<void>) | null = null
31+
32+
export function registerPageMarkHandler(handler: NonNullable<typeof pageMarkHandler>): void {
33+
pageMarkHandler = handler
34+
}
35+
2936
export interface VitestBrowserClient {
3037
rpc: BrowserRPC
3138
ws: WebSocket
@@ -93,6 +100,11 @@ function createClient() {
93100
}
94101
cdp.emit(event, payload)
95102
},
103+
async pageMark(name, options) {
104+
if (pageMarkHandler) {
105+
await pageMarkHandler(name, options)
106+
}
107+
},
96108
async resolveManualMock(url: string) {
97109
// @ts-expect-error not typed global API
98110
const mocker = globalThis.__vitest_mocker__ as ModuleMocker | undefined

packages/browser/src/client/tester/tester.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { BrowserRPC, IframeChannelEvent } from '@vitest/browser/client'
22
import type { FileSpecification } from '@vitest/runner'
3-
import { channel, client, onCancel } from '@vitest/browser/client'
3+
import { channel, client, onCancel, registerPageMarkHandler } from '@vitest/browser/client'
44
import { parse } from 'flatted'
55
import { page, server, userEvent } from 'vitest/browser'
66
import {
@@ -112,6 +112,8 @@ getBrowserState().activeTraceTaskIds = new Set()
112112
getBrowserState().browserTraceAttempts = new Map()
113113
getBrowserState().iframeId = iframeId
114114

115+
registerPageMarkHandler((name, options) => page.mark(name, options))
116+
115117
let contextSwitched = false
116118

117119
async function prepareTestEnvironment(options: PrepareOptions) {

packages/browser/src/node/rpc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
293293
provider,
294294
contextId: sessionId,
295295
sessionId,
296+
mark: async (name: string, options?: any) => {
297+
const tester = (project.browser!.state as BrowserServerState).testers.get(rpcId)
298+
await tester?.pageMark(name, options)
299+
},
296300
triggerCommand: (name: string, ...args: any[]) => {
297301
return project.browser!.triggerCommand(
298302
name as any,

packages/browser/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
TestExecutionMethod,
1212
UserConsoleLog,
1313
} from 'vitest'
14+
import type { MarkOptions } from 'vitest/browser'
1415

1516
export interface WebSocketBrowserHandlers {
1617
resolveSnapshotPath: (testPath: string) => string
@@ -75,6 +76,7 @@ export interface WebSocketBrowserEvents {
7576
createTesters: (options: BrowserTesterOptions) => Promise<void>
7677
cleanupTesters: () => Promise<void>
7778
cdpEvent: (event: string, payload: unknown) => void
79+
pageMark: (name: string, options?: MarkOptions) => Promise<void>
7880
resolveManualMock: (url: string) => Promise<{
7981
url: string
8082
keys: string[]

packages/vitest/src/node/types/browser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { CancelReason } from '@vitest/runner'
33
import type { Awaitable, ParsedStack, TestError } from '@vitest/utils'
44
import type { StackTraceParserOptions } from '@vitest/utils/source-map'
55
import type { Plugin, ViteDevServer } from 'vite'
6-
import type { BrowserCommands, CDPSession } from 'vitest/browser'
6+
import type { BrowserCommands, CDPSession, MarkOptions } from 'vitest/browser'
77
import type { BrowserTraceViewMode } from '../../runtime/config'
88
import type { BrowserTesterOptions } from '../../types/browser'
99
import type { OTELCarrier } from '../../utils/traces'
@@ -353,6 +353,7 @@ export interface BrowserCommandContext {
353353
provider: BrowserProvider
354354
project: TestProject
355355
sessionId: string
356+
mark: (name: string, options?: MarkOptions) => Promise<void>
356357
triggerCommand: <K extends keyof BrowserCommands>(
357358
name: K,
358359
...args: Parameters<BrowserCommands[K]>

test/browser/fixtures/trace/mark.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeEach, test, vi } from 'vitest'
2-
import { page } from 'vitest/browser'
2+
import { commands, page } from 'vitest/browser'
33

44
beforeEach(() => {
55
document.body.innerHTML = ''
@@ -45,6 +45,14 @@ test('kind', async () => {
4545
await page.mark('lifecycle group', { kind: 'mark' })
4646
})
4747

48+
test('custom command', async () => {
49+
document.body.innerHTML = '<span>UI on client side before server side creates mark</span>'
50+
51+
await (commands as any).markFromServer('from server command', 'action')
52+
53+
document.body.innerHTML = '<span>UI on client side after server side creates mark</span>'
54+
})
55+
4856
test('mark function fail', async () => {
4957
await page.mark('failed render group', async () => {
5058
document.body.innerHTML = '<button>Hello</button>'

test/browser/fixtures/trace/vitest.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { defineConfig } from 'vitest/config'
2+
import type { MarkOptions } from 'vitest/browser';
23
import { instances, provider } from '../../settings'
34

45
// TEST_BROWSER=chromium pnpm -C test/browser test-fixtures --root fixtures/trace
@@ -18,6 +19,11 @@ export default defineConfig({
1819
recordCanvas: true,
1920
},
2021
screenshotFailures: false,
22+
commands: {
23+
async markFromServer(context, name: string, kind?: MarkOptions["kind"]) {
24+
await context.mark(name, { kind });
25+
},
26+
},
2127
},
2228
},
2329
})

test/browser/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test-locators": "vitest --root ./fixtures/locators",
2020
"test-locators-custom": "vitest --root ./fixtures/locators-custom",
2121
"test-different-configs": "vitest --root ./fixtures/multiple-different-configs",
22+
"test-trace": "vitest --root ./fixtures/trace",
2223
"test-setup-file": "vitest --root ./fixtures/setup-file",
2324
"test-snapshots": "vitest --root ./fixtures/update-snapshot",
2425
"test-broken-iframe": "vitest --root ./fixtures/broken-iframe",

0 commit comments

Comments
 (0)