UI Mode is Playwright's interactive test runner interface. It provides a browser-based GUI for listing, filtering, and running tests, with integrated live trace display and watch mode. It is launched via npx playwright test --ui.
This page covers the frontend React application (UIModeView), the backend TestServer/TestServerDispatcher, and the WebSocket-based TestServerInterface protocol that connects them. For the standalone Trace Viewer (used outside of UI Mode), see Trace Viewer. For the Recorder and code generation tools, see Recorder and Code Generation.
UI Mode is activated by passing --ui, --ui-host, or --ui-port to the test command. The entry point is runUIMode in packages/playwright/src/runner/testServer.ts273-291
npx playwright test --ui
npx playwright test --ui-host=localhost --ui-port=8080
When --ui-host or --ui-port are set, the UI is opened in the system browser; otherwise, a dedicated Chromium window is launched using openTraceViewerApp.
Launch sequence:
Sources: packages/playwright/src/runner/testServer.ts273-291 packages/playwright/src/program.ts226-243
The UI is split between a Node.js backend process and a React frontend served inside a Chromium window. They communicate over a WebSocket using a JSON-RPC protocol defined by TestServerInterface.
Component architecture:
Sources: packages/playwright/src/runner/testServer.ts45-63 packages/trace-viewer/src/ui/uiModeView.tsx77-553 packages/trace-viewer/src/ui/uiModeTraceView.tsx28-102
TestServer is a thin wrapper that starts the HTTP/WebSocket server and owns a TestServerDispatcher. It is instantiated in innerRunTestServer packages/playwright/src/runner/testServer.ts315-330
TestServerDispatcher implements TestServerInterface and acts as the bridge between the WebSocket transport and the underlying TestRunner. It is defined in packages/playwright/src/runner/testServer.ts90-271
| Class | File | Responsibility |
|---|---|---|
TestServer | testServer.ts:45-63 | Owns dispatcher, starts HTTP server |
TestServerDispatcher | testServer.ts:90-271 | Implements TestServerInterface, delegates to TestRunner |
TestRunner | testRunner.ts | Loads config, lists/runs tests, manages file watcher |
Key TestServerDispatcher details:
transport field is passed directly to startTraceViewerServer, which registers it as a WebSocket handler.initialize intercepts process.stdout/process.stderr and forwards output as stdio events to the frontend.runTests creates a reporter via createReporterForTestServer (using uiModeReporter) and wires it to dispatch report events over the WebSocket.TestRunner are forwarded as testFilesChanged events: packages/playwright/src/runner/testServer.ts111-112When interceptStdio: true is passed to initialize, the dispatcher overrides process.stdout.write and process.stderr.write to send each chunk as a { type, text | buffer } payload via the stdio event. The original write functions are restored in _setInterceptStdio(false) packages/playwright/src/runner/testServer.ts235-270
Sources: packages/playwright/src/runner/testServer.ts90-271
TestServerInterface is defined in packages/playwright/src/isomorphic/testServerInterface.ts25-121 It is isomorphic — shared between the Node.js backend and the browser frontend.
| Method | Purpose |
|---|---|
initialize(params) | Set up stdio interception, file watching, serializer path |
ping() | Keepalive (sent every 30s by TestServerConnection) |
listTests(params) | Return tele-reporter events for all matching tests |
runTests(params) | Execute a set of tests (by ID or location), stream results |
stopTests() | Interrupt the current test run |
runGlobalSetup() | Execute global setup hooks |
runGlobalTeardown() | Execute global teardown hooks |
checkBrowsers() | Return { hasBrowsers } |
installBrowsers() | Run playwright install |
watch(params) | Register files for watch |
open(params) | Open a source file location in VS Code |
resizeTerminal(params) | Resize the terminal (stdout/stderr columns/rows) |
findRelatedTestFiles(params) | Find test files affected by a list of source files |
clearCache() | Clear build and test caches |
listFiles(params) | List test files only (no test cases) |
startDevServer() / stopDevServer() | Manage plugin-provided dev servers |
closeGracefully() | Graceful shutdown |
| Event | Payload | Purpose |
|---|---|---|
report | ReportEntry (JSON tele-reporter event) | Streams test results during a run |
stdio | { type, text?, buffer? } | Forwards intercepted stdout/stderr |
testFilesChanged | { testFiles: string[] } | Signals file changes to trigger re-listing and re-running |
loadTraceRequested | { traceUrl: string } | Used in server mode to load a new trace |
testPaused | { errors: TestError[] } | Sent when a test pauses on error |
Sources: packages/playwright/src/isomorphic/testServerInterface.ts25-137
TestServerConnection is defined in packages/playwright/src/isomorphic/testServerConnection.ts72 and implements TestServerInterface on the client side. It wraps a WebSocketTestServerTransport and provides:
TestServerInterface via _sendMessage.onReport, onStdio, onTestFilesChanged, etc.) via EventEmitter.NoReply variants (e.g., stopTestsNoReply, openNoReply) for fire-and-forget calls packages/playwright/src/isomorphic/testServerConnection.ts168ws URL query parameter on page load packages/trace-viewer/src/ui/uiModeView.tsx49-52Sources: packages/playwright/src/isomorphic/testServerConnection.ts72 packages/trace-viewer/src/ui/uiModeView.tsx49-52
UIModeView is the top-level React component defined in packages/trace-viewer/src/ui/uiModeView.tsx77-553 It owns all UI state and orchestrates the connection, test listing, filtering, and running.
The component renders a horizontal SplitView:
ui-mode-sidebar): contains the toolbar, FiltersView, TestListView, and settings panels.TraceView and XtermWrapper (output panel).Sources: packages/trace-viewer/src/ui/uiModeView.tsx423-553
| State Variable | Type | Purpose |
|---|---|---|
testServerConnection | TestServerConnection | Active WebSocket connection |
teleSuiteUpdater | TeleSuiteUpdater | Processes tele-reporter events into model |
testModel | TeleSuiteUpdaterTestModel | Current test suite model |
testTree | TestTree | Filtered/sorted tree derived from testModel |
filterText | string | Text search filter |
statusFilters | Map<string, boolean> | passed/failed/skipped toggles |
projectFilters | Map<string, boolean> | Per-project enable toggles |
runningState | { testIds, itemSelectedByUser?, completed? } | Active run info |
progress | { total, passed, failed, skipped } | Live progress counts |
selectedItem | { treeItem?, testFile?, testCase? } | Currently selected item for trace display |
watchAll | boolean (persisted) | Whether to watch all test files |
watchedTreeIds | Set<string> | Specific tree items being watched |
singleWorker | boolean (persisted) | Run with workers: 1 |
updateSnapshots | `'all' | 'changed' |
Sources: packages/trace-viewer/src/ui/uiModeView.tsx79-113
Each time the testServerConnection state changes (including on reloadTests), a useEffect runs the main initialization workflow:
Sources: packages/trace-viewer/src/ui/uiModeView.tsx160-216
After listTests completes, the testTree is recomputed via React.useMemo from the current testModel and active filters:
TestTree(filterText, testModel.rootSuite, testModel.loadErrors, projectFilters, pathSeparator, mergeFiles)
.filterTree(filterText, statusFilters, runningState?.testIds)
.sortAndPropagateStatus()
.shortenRoot()
.flattenForSingleProject()
The TestTree class (from @testIsomorphic/testTree) maintains a hierarchy of TreeItem nodes for folders, files, suites, and test cases.
Sources: packages/trace-viewer/src/ui/uiModeView.tsx249-258
The runTests callback packages/trace-viewer/src/ui/uiModeView.tsx260-312 dispatches a test run through a serial commandQueue (a chained Promise) to prevent concurrent runs:
scheduled in the model.runningState = { testIds }.testServerConnection.runTests({ locations, testIds, projects, updateSnapshots, trace: 'on', ... }).report events; each is processed by teleSuiteUpdater.processTestReportEvent.runningState.completed = true.The trace: 'on' parameter forces tracing with { mode: 'on', sources: false, _live: true } on the server side, enabling live trace polling.
runTests parameters sent to server:
{
locations: string[], // regex-escaped file paths
grep: string?,
grepInvert: string?,
testIds: string[],
projects: string[], // enabled projects from projectFilters
updateSnapshots: 'all'|'changed'|'missing'|'none',
trace: 'on',
workers: 1 | undefined // if singleWorker is enabled
}
Sources: packages/trace-viewer/src/ui/uiModeView.tsx260-312 packages/playwright/src/runner/testServer.ts203-212
TraceView packages/trace-viewer/src/ui/uiModeTraceView.tsx28-102 is embedded in UIModeView's main area and shows the Workbench for the currently selected test case.
Trace loading logic:
The traceLocation path is constructed as:
{project.outputDir} / {artifactsFolderName(workerIndex)} / traces / {testCase.id}.json
loadSingleTraceFile fetches contexts?trace=file?path=... from the service worker, which decodes it via the FetchTraceLoaderBackend.
Sources: packages/trace-viewer/src/ui/uiModeTraceView.tsx44-89
Watch mode re-runs tests automatically when source files change. There are two modes controlled by watchAll and watchedTreeIds:
| Mode | Setting | Behavior |
|---|---|---|
| Watch All | watchAll = true | Re-runs any test whose file is in the changed set |
| Watch Selected | watchedTreeIds non-empty | Re-runs tests in watched tree items whose files changed |
When testFilesChanged fires packages/trace-viewer/src/ui/uiModeView.tsx317-378:
listTests.runTests('queue-if-busy', { locations, testIds }).File watching is enabled server-side by passing watchTestDirs: true to initialize. The TestRunner's internal Watcher monitors project testDir paths and emits TestRunnerEvent.TestFilesChanged, which TestServerDispatcher forwards as a testFilesChanged event.
Sources: packages/trace-viewer/src/ui/uiModeView.tsx316-378 packages/playwright/src/runner/testRunner.ts109-115
Shortcuts are registered in a useEffect packages/trace-viewer/src/ui/uiModeView.tsx381-400:
| Shortcut | Mac | Windows/Linux | Action |
|---|---|---|---|
| Toggle output | `Ctrl + `` | `Ctrl + `` | Show/hide XtermWrapper output panel |
| Run tests | F5 | F5 | runVisibleTests() |
| Stop tests | Shift+F5 | Shift+F5 | testServerConnection.stopTestsNoReply({}) |
The XtermWrapper component displays intercepted stdout/stderr from test processes. Data is written to xtermDataSource (a module-level object) via testServerConnection.onStdio:
atob(params.buffer)).stderr chunks set outputContainsError = true, which shows a red dot indicator on the terminal toolbar button.The terminal size is synchronized with the server via testServerConnection.resizeTerminalNoReply({ cols, rows }) when the XtermWrapper is resized, so reporters relying on process.stdout.columns format output correctly.
Sources: packages/trace-viewer/src/ui/uiModeView.tsx130-156
UI Mode accepts configuration via URL query parameters, parsed at module load time packages/trace-viewer/src/ui/uiModeView.tsx49-65:
| Parameter | Description |
|---|---|
ws | WebSocket GUID for connecting to the backend |
server | Base URL of the test server |
arg (multi) | Positional test file filter arguments |
grep | Title grep filter |
grepInvert | Inverted title grep filter |
project (multi) | Project name filters |
workers | Worker count override |
headed | Run in headed mode |
updateSnapshots | Snapshot update mode |
reporter (multi) | Reporter overrides |
pathSeparator | Path separator (important on Windows) |
Persistent UI settings (stored via useSetting / localStorage) include singleWorker, updateSnapshots, mergeFiles, and watch-all.
Sources: packages/trace-viewer/src/ui/uiModeView.tsx49-65 packages/trace-viewer/src/ui/uiModeView.tsx95-112
If checkBrowsers returns { hasBrowsers: false }, a modal dialog is shown offering to run playwright install. The install is triggered via testServerConnection.installBrowsers({}), and checkBrowsers is re-called after it completes to update the UI packages/trace-viewer/src/ui/uiModeView.tsx413-421
Sources: packages/trace-viewer/src/ui/uiModeView.tsx402-421 packages/playwright/src/runner/testServer.ts150-156
If the WebSocket connection closes unexpectedly (testServerConnection.onClose), isDisconnected is set to true and a banner is shown with a link to reload the page packages/trace-viewer/src/ui/uiModeView.tsx131-156 packages/trace-viewer/src/ui/uiModeView.tsx435-438
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.