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
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A powerful browser devtools extension for debugging, visualizing, and controlling test executions in real-time.

Works with **WebdriverIO** and **[Nightwatch.js](./packages/nightwatch-devtools/README.md)** — same backend, same UI, same capture infrastructure.
Works with **WebdriverIO**, **[Nightwatch.js](./packages/nightwatch-devtools/README.md)**, and **[Selenium WebDriver](./packages/selenium-devtools/README.md)** (any test runner) — same backend, same UI, same capture infrastructure.

## Features

Expand Down Expand Up @@ -94,7 +94,12 @@ npm install @wdio/devtools-service
npm install @wdio/nightwatch-devtools
```

> See the [Nightwatch Integration](#nightwatch-integration) section for configuration details.
**Selenium:**
```bash
npm install @wdio/selenium-devtools
```

> See the [Nightwatch Integration](#nightwatch-integration) and [Selenium Integration](#selenium-integration) sections for configuration details.

## Configuration

Expand Down Expand Up @@ -136,6 +141,12 @@ Using [Nightwatch.js](https://nightwatchjs.org/)? A dedicated adapter package br

→ **[`@wdio/nightwatch-devtools`](./packages/nightwatch-devtools/README.md)** — configuration, Cucumber/BDD setup, and limitations.

## Selenium Integration

Using `selenium-webdriver` directly — under Mocha, Jest, Cucumber, or a plain Node script? A runner-agnostic adapter brings the same DevTools UI to any Selenium test suite. The plugin auto-detects the runner and wires test boundaries; no code changes required for hook-aware runners, and a small `DevTools.startTest/endTest` API for plain scripts.

→ **[`@wdio/selenium-devtools`](./packages/selenium-devtools/README.md)** — per-runner setup, configuration options, and screencast details.

## Project Structure

```
Expand All @@ -144,7 +155,8 @@ packages/
├── backend/ # Fastify server with test runner management
├── service/ # WebdriverIO service and reporter
├── script/ # Browser-injected trace collection script
└── nightwatch-devtools/ # Nightwatch adapter plugin
├── nightwatch-devtools/ # Nightwatch adapter plugin
└── selenium-devtools/ # Selenium WebDriver adapter plugin
```

## Contributing
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wdio/devtools-app",
"version": "1.3.2",
"version": "1.4.0",
"description": "Browser devtools extension for debugging WebdriverIO tests.",
"type": "module",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/components/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class DevtoolsSidebar extends Element {
border-right: 1px solid var(--vscode-panel-border) !important;
display: flex;
flex-direction: column;
height: 100%;
}
`
]
Expand Down
17 changes: 15 additions & 2 deletions packages/app/src/components/sidebar/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1 1 auto;
}

header {
Expand Down Expand Up @@ -120,7 +121,9 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
runAll: detail.uid === '*',
framework: this.#getFramework(),
specFile: detail.specFile || this.#deriveSpecFile(detail),
configFile: this.#getConfigPath()
configFile: this.#getConfigPath(),
rerunCommand: this.#getRerunCommand(),
launchCommand: this.#getLaunchCommand()
}
await this.#postToBackend('/api/tests/run', payload)
}
Expand Down Expand Up @@ -199,7 +202,9 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
entryType: 'suite',
runAll: true,
framework: this.#getFramework(),
configFile: this.#getConfigPath()
configFile: this.#getConfigPath(),
rerunCommand: this.#getRerunCommand(),
launchCommand: this.#getLaunchCommand()
})
}

Expand Down Expand Up @@ -277,6 +282,14 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
return options?.configFilePath || options?.configFile
}

#getRerunCommand(): string | undefined {
return this.#getRunnerOptions()?.rerunCommand
}

#getLaunchCommand(): string | undefined {
return this.#getRunnerOptions()?.launchCommand
}

#renderEntry(entry: TestEntry): TemplateResult {
return html`
<wdio-test-entry
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/components/sidebar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface RunnerOptions {
configFile?: string
configFilePath?: string
runCapabilities?: Partial<RunCapabilities>
rerunCommand?: string
launchCommand?: string
}

export interface TestRunDetail {
Expand Down
48 changes: 40 additions & 8 deletions packages/app/src/controller/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,15 @@ export class DataManagerController implements ReactiveController {
}

if (scope === 'clearExecutionData') {
const { uid, entryType } =
const { uid, entryType, clearSuiteTree } =
data as SocketMessage<'clearExecutionData'>['data']
this.clearExecutionData(uid, entryType)
if (clearSuiteTree) {
this.suitesContextProvider.setValue([])
this.#activeRerunTestUid = undefined
rerunState.activeRerunSuiteUid = undefined
this.#lastSeenRunTimestamp = 0
}
this.#host.requestUpdate()
return
}
Expand Down Expand Up @@ -542,14 +548,22 @@ export class DataManagerController implements ReactiveController {

#handleReplaceCommand(oldTimestamp: number, newCommand: CommandLog) {
const current = this.commandsContextProvider.value || []
// Find the last entry with the matching timestamp (most recent retry)
const idx = current.map((c) => c.timestamp).lastIndexOf(oldTimestamp)
// Prefer stable `id` — chained selenium calls share a millisecond.
let idx = -1
const newId = (newCommand as CommandLog & { id?: number }).id
if (typeof newId === 'number') {
idx = current.findIndex(
(c) => (c as CommandLog & { id?: number }).id === newId
)
}
if (idx === -1) {
idx = current.map((c) => c.timestamp).lastIndexOf(oldTimestamp)
}
if (idx !== -1) {
const updated = [...current]
updated[idx] = newCommand
this.commandsContextProvider.setValue(updated)
} else {
// No matching entry found — just append
this.commandsContextProvider.setValue([...current, newCommand])
}
}
Expand All @@ -562,10 +576,28 @@ export class DataManagerController implements ReactiveController {
}

#handleNetworkRequestsUpdate(data: NetworkRequest[]) {
this.networkRequestsContextProvider.setValue([
...(this.networkRequestsContextProvider.value || []),
...data
])
const current = this.networkRequestsContextProvider.value || []
const byId = new Map<string, number>()
current.forEach((r, i) => {
if (r?.id) {
byId.set(r.id, i)
}
})
const next = [...current]
for (const incoming of data) {
if (!incoming?.id) {
next.push(incoming)
continue
}
const existingIdx = byId.get(incoming.id)
if (existingIdx !== undefined) {
next[existingIdx] = incoming
} else {
byId.set(incoming.id, next.length)
next.push(incoming)
}
}
this.networkRequestsContextProvider.setValue(next)
}

#handleMetadataUpdate(data: Metadata) {
Expand Down
6 changes: 5 additions & 1 deletion packages/app/src/controller/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ export interface SocketMessage<
data: T extends keyof TraceLog
? TraceLog[T]
: T extends 'clearExecutionData'
? { uid?: string; entryType?: 'suite' | 'test' }
? {
uid?: string
entryType?: 'suite' | 'test'
clearSuiteTree?: boolean
}
: T extends 'replaceCommand'
? { oldTimestamp: number; command: CommandLog }
: unknown
Expand Down
4 changes: 3 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wdio/devtools-backend",
"version": "1.3.1",
"version": "1.4.0",
"description": "Backend service to spin up WebdriverIO Devtools",
"author": "Christian Bromann <mail@bromann.dev>",
"license": "MIT",
Expand Down Expand Up @@ -32,9 +32,11 @@
"fastify": "^5.8.4",
"get-port": "^7.1.0",
"import-meta-resolve": "^4.1.0",
"shell-quote": "^1.8.3",
"tree-kill": "^1.2.2"
},
"devDependencies": {
"@types/shell-quote": "^1.7.5",
"@types/ws": "^8.18.1",
"nodemon": "^3.1.14",
"ws": "^8.18.3"
Expand Down
Loading
Loading