Electrobun Testing
Use when writing Electrobun tests, adding test coverage to the Kitchen Sink, implementing the defineTest() pattern, generating new test suites, understanding what the kitchen sink tests, or reverse-engineering component behaviour from test source. Activates on test authoring, test framework, or test-driven development questions.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install milady-ai-milaidy-electrobun-testing
Repository
Skill path: .claude/plugins/electrobun-dev/skills/electrobun-testing
Use when writing Electrobun tests, adding test coverage to the Kitchen Sink, implementing the defineTest() pattern, generating new test suites, understanding what the kitchen sink tests, or reverse-engineering component behaviour from test source. Activates on test authoring, test framework, or test-driven development questions.
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, Tech Writer, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: milady-ai.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install Electrobun Testing into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/milady-ai/milaidy before adding Electrobun Testing to shared team environments
- Use Electrobun Testing for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: Electrobun Testing
description: Use when writing Electrobun tests, adding test coverage to the Kitchen Sink, implementing the defineTest() pattern, generating new test suites, understanding what the kitchen sink tests, or reverse-engineering component behaviour from test source. Activates on test authoring, test framework, or test-driven development questions.
version: 1.0.0
---
# Electrobun Testing
Tests for Electrobun APIs live in the Kitchen Sink app (`kitchen/src/tests/`).
## Test Definition Pattern
All tests use `defineTest()` from the test framework:
```typescript
// kitchen/src/tests/window.test.ts
import { defineTest } from "../test-framework/defineTest";
export const windowTests = [
defineTest({
id: "window-creation-with-url", // stable slug — must be unique
title: "Window creation with URL",
category: "BrowserWindow",
description: "Verifies BrowserWindow can be created with a URL",
interactive: false, // automated: no human needed
apiSurface: ["BrowserWindow"], // APIs exercised (for manifest)
async run({ assert, log }) {
const win = new BrowserWindow({
title: "Test Window",
url: "views://test-harness/index.html",
hidden: true,
rpc: testRpc,
});
log("Window created, id:", win.id);
assert(win.id > 0, "Window should have valid id");
await win.close();
},
}),
];
```
### defineTest() Fields
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | ✅ | Stable slug (kebab-case, globally unique) |
| `title` | string | ✅ | Human-readable display name |
| `category` | string | ✅ | API grouping (e.g. "BrowserWindow", "Utils") |
| `description` | string | ✅ | One-sentence description |
| `interactive` | boolean | ✅ | `true` if requires human input |
| `apiSurface` | string[] | ✅ | Electrobun APIs exercised |
| `run` | async function | ✅ | Test body (see below) |
| `playgroundRoute` | string? | — | Playground HTML path if has UI |
| `uiSelectors` | string[] | — | CSS selectors used in playground |
| `platformCaveats` | string[] | — | e.g. `["macOS only"]` |
### Test Body API
```typescript
async run({ assert, log, skip, fail }) {
// assert(condition, message) — fails test if false
assert(value === expected, "description of what was expected");
// log(message) — appears in test runner UI
log("Window id:", win.id);
// skip(reason) — marks test as skipped (not failed)
if (process.platform === "win32") skip("Not supported on Windows");
// fail(message) — immediately fail with message
fail("Should not reach here");
}
```
## Registering Test Suites
After writing a test file, add it to the aggregator:
```typescript
// kitchen/src/tests/index.ts
import { windowTests } from "./window.test";
import { myNewTests } from "./my-new-feature.test";
export const allTests = [
...windowTests,
...myNewTests, // ← add here
];
```
Then regenerate the manifest:
```bash
cd kitchen
npx tsx scripts/generate-manifest.ts
```
## Writing Automated Tests
Automated tests (`interactive: false`) run entirely in code — no human in the loop.
### Pattern: Test a BrowserWindow method
```typescript
defineTest({
id: "window-set-title",
title: "BrowserWindow.setTitle()",
category: "BrowserWindow",
description: "Verifies setTitle changes the window title",
interactive: false,
apiSurface: ["BrowserWindow"],
async run({ assert, log }) {
const win = new BrowserWindow({
title: "Original Title",
url: "views://test-harness/index.html",
hidden: true,
rpc: testRpc,
});
win.setTitle("New Title");
// Observe via evaluateJavascript or just test the API doesn't throw
log("Title changed successfully");
assert(true, "setTitle completed without error");
await win.close();
},
}),
```
### Pattern: Test RPC round-trip
```typescript
defineTest({
id: "rpc-request-response",
title: "RPC request-response round trip",
category: "RPC",
description: "Verifies bun-side request handler is callable from webview",
interactive: false,
apiSurface: ["BrowserView"],
async run({ assert, log }) {
const win = new BrowserWindow({
title: "RPC Test",
url: "views://test-harness/index.html",
hidden: true,
rpc: testRpc,
});
// Use evaluateJavascriptWithResponse to trigger webview-side RPC call
const result = await win.webview.rpc.request.evaluateJavascriptWithResponse({
script: `electrobun.rpc.request.someBunFunction({ a: 2, b: 3 })`,
});
assert(result === 5, `Expected 5, got ${result}`);
log("RPC round trip passed");
await win.close();
},
}),
```
### Pattern: Test Utils / system APIs
```typescript
defineTest({
id: "utils-clipboard-text",
title: "Clipboard read/write text",
category: "Utils",
description: "Verifies clipboardWriteText and clipboardReadText round-trip",
interactive: false,
apiSurface: ["Utils"],
async run({ assert, log }) {
Utils.clipboardWriteText("electrobun-test-value");
const text = Utils.clipboardReadText();
assert(text === "electrobun-test-value", `Expected clipboard text, got: ${text}`);
Utils.clipboardClear();
log("Clipboard round-trip passed");
},
}),
```
### Pattern: Test navigation rules
```typescript
defineTest({
id: "navigation-block-rule",
title: "setNavigationRules blocks disallowed URL",
category: "Navigation",
description: "Verifies last-match-wins navigation rules",
interactive: false,
apiSurface: ["BrowserView"],
async run({ assert, log }) {
const win = new BrowserWindow({ url: "views://test-harness/index.html", hidden: true, rpc: testRpc });
win.webview.setNavigationRules([
{ match: "*", allow: false }, // block all
{ match: "views://*", allow: true }, // allow views:// (last match wins)
]);
// Navigate to allowed URL — should succeed
await win.webview.loadURL("views://test-harness/index.html");
log("Navigation rules applied");
await win.close();
},
}),
```
## Writing Interactive Tests
Interactive tests (`interactive: true`) open a playground window and walk the user through a verification flow.
```typescript
defineTest({
id: "dialog-open-file",
title: "Open file dialog",
category: "Dialogs",
description: "Opens the native file picker and verifies a path is returned",
interactive: true,
apiSurface: ["Utils"],
playgroundRoute: "playgrounds/file-dialog/index.html",
uiSelectors: ["#openDialogBtn", "#result"],
async run({ assert, log, waitForReady, waitForVerify }) {
// Open the playground window
const playground = new BrowserWindow({
title: "File Dialog Playground",
url: "views://playgrounds/file-dialog/index.html",
rpc: playgroundRpc,
});
// Wait for user to click Start in the modal
await waitForReady();
// Instructions have been shown; user now interacts with playground window
// Wait for user to click Pass/Fail/Retest
const { action, notes } = await waitForVerify();
assert(action === "pass", `User marked ${action}: ${notes}`);
await playground.close();
},
}),
```
## Full API Surface Tested by Kitchen Sink
### BrowserWindow
`new BrowserWindow(options)`, `getById(id)`, `webview`, `setTitle`, `close`, `focus`, `show`, `minimize`, `unminimize`, `isMinimized`, `maximize`, `unmaximize`, `isMaximized`, `setFullScreen`, `isFullScreen`, `setAlwaysOnTop`, `isAlwaysOnTop`, `setVisibleOnAllWorkspaces`, `isVisibleOnAllWorkspaces`, `setPosition`, `setSize`, `setFrame`, `getFrame`, `getPosition`, `getSize`, `setPageZoom`, `getPageZoom`, `on('close'|'move'|'resize'|'blur'|'focus')`
### BrowserView
`BrowserView.defineRPC(schema)`, `new BrowserView(options)`, `executeJavascript`, `loadURL`, `loadHTML`, `setNavigationRules`, `stopFindInPage`, `openDevTools`, `closeDevTools`, `toggleDevTools`, `setPageZoom`, `getPageZoom`, `remove`, `getById`, `getAll`, `on('will-navigate'|'did-navigate'|'dom-ready')`, `rpc.request.evaluateJavascriptWithResponse`
### Tray
`new Tray(options)`, `setTitle`, `setImage`, `setMenu`, `setVisible`, `getBounds`, `remove`, `on('tray-clicked')`, `getById`, `getAll`, `removeById`
### ApplicationMenu / ContextMenu
`ApplicationMenu.setApplicationMenu(menu)`, `ContextMenu.showContextMenu(menu)`, `Electrobun.events.on('application-menu-clicked')`, `Electrobun.events.on('context-menu-clicked')`
### Utils
`moveToTrash`, `showItemInFolder`, `openExternal`, `openPath`, `setDockIconVisible`, `isDockIconVisible`, `showNotification`, `quit`, `openFileDialog`, `showMessageBox`, `clipboardReadText`, `clipboardWriteText`, `clipboardReadImage`, `clipboardWriteImage`, `clipboardClear`, `clipboardAvailableFormats`, `paths.*`
### Session
`Session.fromPartition(name)`, `Session.defaultSession`, `session.cookies.set/get/remove/clear`
### Screen
`Screen.getPrimaryDisplay()`, `Screen.getAllDisplays()`, `Screen.getCursorScreenPoint()`
### GlobalShortcut
`GlobalShortcut.register(accelerator, handler)`, `GlobalShortcut.unregister(accelerator)`, `GlobalShortcut.unregisterAll()`
### Updater
`Updater.getLocal`, `Updater.onStatusChange`, `Updater.checkForUpdate`, `Updater.downloadUpdate`, `Updater.updateInfo`, `Updater.applyUpdate`, `Updater.getStatusHistory`, `Updater.clearStatusHistory`
Updater status values: `checking`, `update-available`, `downloading`, `update-ready`, `no-update`, `error`
### View-side (electrobun/view)
`Electroview.defineRPC(schema)`, `new Electroview({ rpc })`, `electrobun.rpc.request.*`, `electrobun.rpc.send.*`, `evaluateJavascriptWithResponse({ script })`
## Platform Caveats to Document in Tests
Always add a `platformCaveats` array when a test is platform-specific:
```typescript
platformCaveats: ["macOS only"],
platformCaveats: ["requires CEF renderer"],
platformCaveats: ["unreliable on Linux window managers"],
platformCaveats: ["ARM Windows: commented out due to VM crashes"],
```
## Generating and Validating After Changes
```bash
cd kitchen
# After adding new defineTest() calls:
npx tsx scripts/generate-manifest.ts
# Verify everything is consistent:
npx tsx scripts/validate-manifest.ts
# Checks:
# 1. Every test in index.ts has manifest entries
# 2. Every playground route with a test is represented
# 3. All entries have required fields
# 4. No duplicate IDs
```