Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/api/browser/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,25 @@ declare module 'vitest/browser' {
Custom functions will override built-in ones if they have the same name.
:::

### Recording trace markers

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.

```ts
import type { BrowserCommand } from 'vitest/node'

export const uploadFixture: BrowserCommand<[name: string]> = async (
context,
name,
) => {
await context.mark(`upload start: ${name}`, { kind: 'action' })
// ... do server-side work
await context.mark(`upload done: ${name}`, { kind: 'action' })
}
```

`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.

### Custom `playwright` commands

Vitest exposes several `playwright` specific properties on the command context.
Expand Down
2 changes: 2 additions & 0 deletions docs/api/browser/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ await page.mark('submit flow', async () => {

::: tip
This method is useful only when [`browser.trace`](/config/browser/trace) is enabled.

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.
:::

### frameLocator
Expand Down
12 changes: 12 additions & 0 deletions packages/browser/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ModuleMocker } from '@vitest/mocker/browser'
import type { CancelReason } from '@vitest/runner'
import type { BirpcReturn } from 'birpc'
import type { MarkOptions } from 'vitest/browser'
import type { WebSocketBrowserEvents, WebSocketBrowserHandlers } from '../types'
import type { IframeOrchestrator } from './orchestrator'
import { createBirpc } from 'birpc'
Expand All @@ -26,6 +27,12 @@ export function onCancel(callback: (reason: CancelReason) => void): void {
onCancelCallbacks.push(callback)
}

let pageMarkHandler: ((name: string, options?: MarkOptions) => Promise<void>) | null = null

export function registerPageMarkHandler(handler: NonNullable<typeof pageMarkHandler>): void {
pageMarkHandler = handler
}

export interface VitestBrowserClient {
rpc: BrowserRPC
ws: WebSocket
Expand Down Expand Up @@ -93,6 +100,11 @@ function createClient() {
}
cdp.emit(event, payload)
},
async pageMark(name, options) {
if (pageMarkHandler) {
await pageMarkHandler(name, options)
}
},
async resolveManualMock(url: string) {
// @ts-expect-error not typed global API
const mocker = globalThis.__vitest_mocker__ as ModuleMocker | undefined
Expand Down
4 changes: 3 additions & 1 deletion packages/browser/src/client/tester/tester.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BrowserRPC, IframeChannelEvent } from '@vitest/browser/client'
import type { FileSpecification } from '@vitest/runner'
import { channel, client, onCancel } from '@vitest/browser/client'
import { channel, client, onCancel, registerPageMarkHandler } from '@vitest/browser/client'
import { parse } from 'flatted'
import { page, server, userEvent } from 'vitest/browser'
import {
Expand Down Expand Up @@ -112,6 +112,8 @@ getBrowserState().activeTraceTaskIds = new Set()
getBrowserState().browserTraceAttempts = new Map()
getBrowserState().iframeId = iframeId

registerPageMarkHandler((name, options) => page.mark(name, options))

let contextSwitched = false

async function prepareTestEnvironment(options: PrepareOptions) {
Expand Down
4 changes: 4 additions & 0 deletions packages/browser/src/node/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
provider,
contextId: sessionId,
sessionId,
mark: async (name: string, options?: any) => {
const tester = (project.browser!.state as BrowserServerState).testers.get(rpcId)
await tester?.pageMark(name, options)
},
triggerCommand: (name: string, ...args: any[]) => {
return project.browser!.triggerCommand(
name as any,
Expand Down
2 changes: 2 additions & 0 deletions packages/browser/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
TestExecutionMethod,
UserConsoleLog,
} from 'vitest'
import type { MarkOptions } from 'vitest/browser'

export interface WebSocketBrowserHandlers {
resolveSnapshotPath: (testPath: string) => string
Expand Down Expand Up @@ -75,6 +76,7 @@ export interface WebSocketBrowserEvents {
createTesters: (options: BrowserTesterOptions) => Promise<void>
cleanupTesters: () => Promise<void>
cdpEvent: (event: string, payload: unknown) => void
pageMark: (name: string, options?: MarkOptions) => Promise<void>
resolveManualMock: (url: string) => Promise<{
url: string
keys: string[]
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/types/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CancelReason } from '@vitest/runner'
import type { Awaitable, ParsedStack, TestError } from '@vitest/utils'
import type { StackTraceParserOptions } from '@vitest/utils/source-map'
import type { Plugin, ViteDevServer } from 'vite'
import type { BrowserCommands, CDPSession } from 'vitest/browser'
import type { BrowserCommands, CDPSession, MarkOptions } from 'vitest/browser'
import type { BrowserTraceViewMode } from '../../runtime/config'
import type { BrowserTesterOptions } from '../../types/browser'
import type { OTELCarrier } from '../../utils/traces'
Expand Down Expand Up @@ -353,6 +353,7 @@ export interface BrowserCommandContext {
provider: BrowserProvider
project: TestProject
sessionId: string
mark: (name: string, options?: MarkOptions) => Promise<void>
triggerCommand: <K extends keyof BrowserCommands>(
name: K,
...args: Parameters<BrowserCommands[K]>
Expand Down
10 changes: 9 additions & 1 deletion test/browser/fixtures/trace/mark.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { beforeEach, test, vi } from 'vitest'
import { page } from 'vitest/browser'
import { commands, page } from 'vitest/browser'

beforeEach(() => {
document.body.innerHTML = ''
Expand Down Expand Up @@ -45,6 +45,14 @@ test('kind', async () => {
await page.mark('lifecycle group', { kind: 'mark' })
})

test('custom command', async () => {
document.body.innerHTML = '<span>UI on client side before server side creates mark</span>'

await (commands as any).markFromServer('from server command', 'action')

document.body.innerHTML = '<span>UI on client side after server side creates mark</span>'
})

test('mark function fail', async () => {
await page.mark('failed render group', async () => {
document.body.innerHTML = '<button>Hello</button>'
Expand Down
6 changes: 6 additions & 0 deletions test/browser/fixtures/trace/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defineConfig } from 'vitest/config'
import type { MarkOptions } from 'vitest/browser';
import { instances, provider } from '../../settings'

// TEST_BROWSER=chromium pnpm -C test/browser test-fixtures --root fixtures/trace
Expand All @@ -18,6 +19,11 @@ export default defineConfig({
recordCanvas: true,
},
screenshotFailures: false,
commands: {
async markFromServer(context, name: string, kind?: MarkOptions["kind"]) {
await context.mark(name, { kind });
},
},
},
},
})
1 change: 1 addition & 0 deletions test/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"test-locators": "vitest --root ./fixtures/locators",
"test-locators-custom": "vitest --root ./fixtures/locators-custom",
"test-different-configs": "vitest --root ./fixtures/multiple-different-configs",
"test-trace": "vitest --root ./fixtures/trace",
"test-setup-file": "vitest --root ./fixtures/setup-file",
"test-snapshots": "vitest --root ./fixtures/update-snapshot",
"test-broken-iframe": "vitest --root ./fixtures/broken-iframe",
Expand Down
57 changes: 51 additions & 6 deletions test/browser/specs/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ test('trace view artifacts', async () => {
],
},
"mark.test.ts": {
"custom command": "passed",
"helper": "passed",
"kind": "passed",
"locator.mark": "passed",
Expand Down Expand Up @@ -459,6 +460,28 @@ test('trace view artifacts', async () => {
],
},
"mark.test.ts": {
"custom command": [
{
"entries": [
{
"kind": "action",
"name": "from server command",
"snapshot": {},
},
],
},
{
"entries": [
{
"kind": "lifecycle",
"location": "mark.test.ts:48",
"name": "vitest:onAfterRetryTask",
"snapshot": {},
"status": "pass",
},
],
},
],
"helper": [
{
"entries": [
Expand Down Expand Up @@ -617,7 +640,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "mark",
"location": "mark.test.ts:49",
"location": "mark.test.ts:57",
"name": "failed render group",
"range": {
"phase": "start",
Expand All @@ -630,7 +653,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "mark",
"location": "mark.test.ts:49",
"location": "mark.test.ts:57",
"name": "failed render group",
"range": {
"phase": "end",
Expand All @@ -644,7 +667,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "lifecycle",
"location": "mark.test.ts:51",
"location": "mark.test.ts:59",
"name": "vitest:onAfterRetryTask",
"snapshot": {},
"status": "fail",
Expand Down Expand Up @@ -2013,6 +2036,28 @@ test('trace view artifacts', async () => {
],
},
"mark.test.ts": {
"custom command": [
{
"entries": [
{
"kind": "action",
"name": "from server command",
"snapshot": {},
},
],
},
{
"entries": [
{
"kind": "lifecycle",
"location": "mark.test.ts:48",
"name": "vitest:onAfterRetryTask",
"snapshot": {},
"status": "pass",
},
],
},
],
"helper": [
{
"entries": [
Expand Down Expand Up @@ -2169,7 +2214,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "mark",
"location": "mark.test.ts:49",
"location": "mark.test.ts:57",
"name": "failed render group",
"range": {
"phase": "start",
Expand All @@ -2182,7 +2227,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "mark",
"location": "mark.test.ts:49",
"location": "mark.test.ts:57",
"name": "failed render group",
"range": {
"phase": "end",
Expand All @@ -2196,7 +2241,7 @@ test('trace view artifacts', async () => {
"entries": [
{
"kind": "lifecycle",
"location": "mark.test.ts:51",
"location": "mark.test.ts:59",
"name": "vitest:onAfterRetryTask",
"snapshot": {},
"status": "fail",
Expand Down
Loading