app-testing
Skill9 starsChanged 4 days agoMITView on GitHub
How to launch the real Walph app in dev mode and drive its UI (click, type, fire menu commands, read the DOM, take screenshots, read logs) through the Chrome DevTools Protocol, using the scripts bundled in this skill. Use to verify a feature in the running app, reproduce a visual bug, or validate acceptance criteria.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- dsonders/claude-skillstheir own version, first seen Oct 20, 2026
- alfredang/skillstheir own version, first seen Feb 19, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- alfredang/skills★ 2same name
- dsonders/claude-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- claude-panelSkill
- commands-settingsSkill
- containersSkill
- debuggerSkill
- files-git-searchSkill
- i18nSkill
- lspSkill
- main-ipcSkill
- main-processesSkill
- packaging-releaseSkill
- renderer-componentsSkill
- renderer-editorSkill
- renderer-storeSkill
- renderer-stylesSkill
- renderer-terminalSkill
- workspacesSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 91
Settings: write <userData>/settings.json, <userData>/keybindings.json or <repo>/.walph/settings.json directly (JSONC) — main watches them …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Running and Driving Walph
Scripts live in ${CLAUDE_SKILL_DIR}/scripts/ (macOS and Linux). Temporary files go to $WALPH_TEST_DIR
(default ${TMPDIR:-/tmp}/walph-test): dev.log, fixtures, screenshots.
S=${CLAUDE_SKILL_DIR}/scripts
FIX=$($S/make-fixture.sh) # git repo with pending changes
$S/launch.sh "$FIX" # start the app (dev) and open the folder
node $S/cdp.mjs eval "document.title"
$S/stop.sh # ALWAYS at the endQuick Index
| Script | Purpose |
make-fixture.sh [dir] | Repo with src/index.ts, src/utils/math.ts (modified), README.md, notas.txt (untracked) |
launch.sh [dir] | Kills the previous instance, runs electron-vite dev -- [dir] without ELECTRON_RUN_AS_NODE, CDP on port 9333, main inspector on 9334. Set WALPH_USER_DATA_DIR=<dir> for an isolated --user-data-dir (settings, keybindings, localStorage) |
cdp.mjs eval|wait|open|add|type|key|shot|logs|pages | Renderer: evaluate JS, wait for a condition, open a folder in the target workspace, add a folder as a new workspace tab, type, keys, screenshot, console errors, list views |
main.mjs command <id> | Fires a registry command id (editor.save, workbench.closeTab, workbench.openFolder, workbench.showCommandPalette, workbench.quickOpen, editor.goToLine, view.explorer, view.search, view.git, view.toggleSidebar, terminal.toggle, terminal.new, workspace.add, workspace.select1…9, workspace.next, workspace.close…; full list: CommandId in src/shared/commands.ts) |
main.mjs views | Lists the workspace views of every window: index, window id, active/inactive, title, url |
WALPH_PAGE=<sel> (env, both scripts) | Picks the target workspace view (one WebContentsView per workspace, several per window). Unset = the ACTIVE (visible) view — of the focused window for main.mjs, of the first window that has one for cdp.mjs. ws:<n> = n-th tab of the WorkspaceBar (1-based, like ⌘n), ws:<name> = tab by folder name, a number = index (cdp.mjs pages for cdp, main.mjs views for main — the two lists are ordered differently), =text = exact title, anything else = title substring (titles are <folder> — Walph). A dormant workspace has no view: select it with main.mjs command workspace.select<n> first. |
main.mjs eval "<expr>" | Evaluates in the main process (electron already in scope) |
stop.sh | Stops the app |
Recipes
Click an explorer item / open a file
node $S/cdp.mjs eval "[...document.querySelectorAll('.tree .tree-row')].find(r => r.querySelector('.tree-label')?.textContent === 'src').click()"
node $S/cdp.mjs wait "[...document.querySelectorAll('.tree-label')].some(l => l.textContent === 'math.ts')"Edit and save: open the file (focus goes to CodeMirror) → cdp.mjs type "text" → main.mjs command editor.save →
check the file on disk with cat.
Fill a React-controlled input (type needs focus; to set it directly):
const el = document.querySelector('.commit-box textarea')
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(el, 'message')
el.dispatchEvent(new Event('input', { bubbles: true }))Terminal: main.mjs command terminal.toggle → wait for the prompt → type "command" → key Enter. Read output:
eval "[...document.querySelectorAll('.xterm-rows > div')].map(d => d.textContent.trimEnd()).filter(Boolean)".
Screenshot: cdp.mjs shot $WALPH_TEST_DIR/s1.png, then open it with the Read tool to see it.
Gotchas (all of them happened)
- **Menu shortcuts (⌘S, ⌘W, Ctrl+
…) don't fire with synthetic CDP keys**, and macOS blocksosascriptkeystrokes without accessibility permission. Usemain.mjs command<id>`. - WebGL hides the terminal text from the DOM:
.xterm-rowsis empty with the defaultterminal.gpuAcceleration: "auto". Write{"terminal.gpuAcceleration": "off"}to the isolatedsettings.jsonbefore reading the buffer from the DOM (or use screenshots / the find widget count). typedoesn't send Enter to xterm (\nfrominsertTextis ignored). Usekey Enter.- Text typed before the shell prompt shows up twice. Wait for the prompt.
- Claude panel without network: export
WALPH_FAKE_CLAUDE=<script.json>beforelaunch.sh(format at the top ofsrc/main/claude/fake/script.ts); never drive the real Claude. Hidden windows reportdocument.hasFocus()per the page emulation — override it and dispatchfocus/blurto simulate the OS focus. - Several workspaces per window: the host window is a
BaseWindowwith NO webContents; each workspace is aWebContentsViewrunning the whole renderer, soBrowserWindow.getAllWindows()is empty andevalin main must useelectron.BaseWindow/electron.webContents. Only the active view is visible, the others keep running (CDP andmain.mjsreach them viaWALPH_PAGE). Restored workspaces are dormant (tab only) until activated. Add tabs withnode $S/cdp.mjs add <dir>(the new one becomes active, and the default target moves with it), switch withmain.mjs command workspace.select2, list withcdp.mjs pages/main.mjs views. The defaultcdp.mjstarget is the active workspace view;main.mjs commandwith a window index/title means a view (a title is the view's, i.e. the folder name). - Windows are hidden by default (
WALPH_HIDDEN_WINDOWS=1, set bylaunch.sh; no Dock icon, never steals focus). CDP, screenshots andmain.mjswork the same. Consequences:BaseWindow.isFocused()/getFocusedWindow()are always false/null — to check "focuses the other window", spy onBaseWindow.prototype.focusviamain.mjs eval; native dialogs (dialog.showMessageBox) still pop up on screen — stub them viamain.mjs eval. RunWALPH_HIDDEN_WINDOWS=0 launch.sh …only when a real on-screen check is needed (then the user may click on it). - Changes in
src/rendererarrive via HMR; changes insrc/main/src/preloadrestart the app (the last folder is reopened — wait for.tree-rowagain). launch.sh <dir>opens the folder through argv (electron-vite dev -- <dir>→workspace:initial, delivered only once per main process).cdp.mjs openreplaces the folder of the target workspace through main (main.mjs open <dir>→workspace:openRequested, the Abrir Recente path; a dirty buffer makes the renderer ask first);cdp.mjs addopens it as a new workspace tab instead. If a change alters how the app opens folders, update these scripts.- Command ids are the registry ids (
CommandIdinsrc/shared/commands.ts); the old names (save,showExplorer…) no longer exist. - userData: unpackaged runs without
--user-data-diruse<appData>/Walph-dev(the installed app keepsWalph). The app is single-instance per userData: a second launch with the same dir forwards its argv (folder) to the running one and exits — solaunch.shkills the previous instance first, and parallel instances need distinct dirs. - Settings: write
<userData>/settings.json,<userData>/keybindings.jsonor<repo>/.walph/settings.jsondirectly (JSONC) — main watches them and pushes the change within ~100 ms, no reload needed. Launch with an isolated--user-data-dirso the user's real settings/theme don't leak into the test. Commands:workbench.openSettings(the Settings modal,[data-testid="settings-modal"], rows[data-setting-key], controls[data-testid="setting-<key>"], scope[data-scope], sections[data-section]),workbench.openSettingsJson(the user JSON file),workbench.openWorkspaceSettings,workbench.openKeybindings,workbench.selectTheme. The active theme is also cached inlocalStorage(walph.theme) for the first paint. - Native dialogs (
dialog.showMessageBox, open folder) can't be automated through CDP. Test the logic behind them or mark the item as manual verification.
Unit tests that run real git or compare real paths (Windows-safe)
src/main/testing/testEnv.tsis the one helper for tests (never imported by product code):isolatedGitConfigEnv()(GIT_CONFIG_NOSYSTEM,GIT_TERMINAL_PROMPTandGIT_CONFIG_GLOBALpointing to an empty file) andmakeRealTempDir(prefix)(mkdtemp+realpathSync.native). Never useos.devNull//dev/nullasGIT_CONFIG_GLOBAL: on Windows it is\\.\nuland git fails withunable to access. Never use the JSrealpathSyncfor a temp dir: it keeps the 8.3 name (RUNNER~1) whilefs/promisesrealpath(used by product code) returns the long one.- Pure functions that depend on the OS take
platformexplicitly (parseFileUrlList(text, platform),pathKey(path, platform)) and the tests cover'win32'inputs on every host.node:pathparity tests pin the cwd (vi.spyOn(process, 'cwd')) and compare withposix.resolve(cwd, input)/win32.resolve(cwd, input), because the bareposix.resolveuses the host cwd (Windows:D:\x→/x).
Test typecheck projects
tsconfig.node.json/tsconfig.web.json exclude test files and the testing helper dirs, and enable exactOptionalPropertyTypes
and noUncheckedIndexedAccess. tsconfig.node.test.json/tsconfig.web.test.json include everything and keep only
exactOptionalPropertyTypes (tests index fixtures freely; no ! churn). In a test, an explicit undefined on an optional
prop that the code treats as a real value needs | undefined on the fixture type, not a cast. pnpm typecheck runs all
five projects. The no-unnecessary-condition/prefer-nullish-coalescing lint rules do not apply to tests.
Shared unit test helpers (use them; never hand-roll a copy)
- Renderer
window.api:src/renderer/src/test/fakeApi.tsinstallFakeApi(overrides)installs a typed fake (every method avi.fn, idle defaults, overrides asDeepPartial<WalphApi>) and returns{ api, emit, reset };emit.<ns>.on<Event>(...)fires a subscribed channel. A newWalphApinamespace fails typecheck until it is listed there.src/renderer/src/test/resetStore.tsresetStore()restores the initial store state; import the store dynamically afterinstallFakeApi(the store readswindow.apiat load). Never stubwindow.apiwith aProxyor anunknowncast in a test. The defaultplatformis'linux'(case-sensitive paths); pass'darwin'only when the test needs it.emitwith no subscriber throws, and sync methods return neutral values, not promises. Type local spies with the real signature (vi.fn<GitApi['branches']>()) and build complete fixtures (src/renderer/src/test/gitFixtures.ts) instead ofas never. - Renderer setup:
src/renderer/src/test/setup.ts(stubsResizeObserver, setsIS_REACT_ACT_ENVIRONMENT) runs for every renderer test, in thevmThreadspool.restoreMocks,unstubGlobalsandunstubEnvsare global invitest.config.mts: no manual restore inafterEach, and no test may rely on state leaking from another. - Renderer fixtures and waits (
src/renderer/src/test/):tabFixtures.tsfileTab(patch)builds a complete cleanFileTab;toastTitles()(toastTitles.ts) lists the visible toast titles;deferred<T>()(deferred.ts) is a promise the test resolves or rejects to order async answers (stale-response guards). Action-module tests drive the real store,documentStoreandlspSync(loadDocument,applyTabEdits) instead of mocking the editor; module-level state of the action (read failures, format-dirty) is cleared by closing the tabs with the module'sstart…Reactionsrunning.vi.resetModules()does not clearvi.mockfactories: a test that reloads the module under test imports@shared/i18nagain to change its locale. - CodeMirror extensions and hooks (
src/renderer/src/test/):editorHarness.tsmountEditor({ doc, extensions, tabId, path, host, selection })mounts a realEditorViewwithlspTabIdset, the tab opened inlspSync(path: null= a tab with no LSP path, never opened inlspSync) and anLspHostof spies (serverForready by default);unmountEditors()inafterEachalso restores what the harness patched. Each mounted editor needs its owntabId. It stubs the jsdom gaps CodeMirror hits (text measure,scrollIntoView); a test of canvas code also stubsmatchMedia(withvi.stubGlobal) andgetContext. LSP calls go throughinstallFakeApi({ lsp: { completion, ... } }); timer-driven extensions usevi.useFakeTimers()withawait vi.advanceTimersByTimeAsync(ms).renderHook.tsxrenderHook(hook, props)runs a hook withcurrent/rerender(no@testing-library); hooks that need rendered elements (refs, DOM events, scroll containers) mount a small component withmountfromdom.ts, render the elements the hook attaches to and read the result from the DOM or from auseEffectcallback. - Main IPC handlers:
src/main/testing/fakeIpc.tscreateIpcHarness()giveselectronMock(spread intovi.mock('electron', ...)),invoke(channel, sender, ...args),emit(channel, sender, ...args)andregistered(); create it withawait vi.hoisted(async () => (await import('../testing/fakeIpc')).createIpcHarness()).fakeWebContents()is anEventEmitterwith spiedon/once/emitandsend/isDestroyedmocks, enough to driveonViewReset. - Channel registry:
IPC_CHANNELSinsrc/shared/ipc.tsis derived fromipcSpec(nothing to maintain by hand);src/main/ipc/registry.test.tschecks everyregister*Handlersregisters exactly that set. A new channel only needs its spec entry; a newregister*Handlersgoes into that test.src/preload/buildApi.test.tsdrives the bridge with a fakeipcRenderer. - Git repos:
src/main/testing/gitRepo.tsmakeTempRepo(files, { commit, message })(branchmain, real temp path, caller removes it),git(cwd, ...args)(isolated config,GIT_TERMINAL_PROMPT=0, test identity, mirrorse2e/helpers.ts) anduseGitTestEnv()for product code that runs git itself (returns the restore function). - Async ordering (main):
src/main/testing/deferred.tsdeferred<T = void>()is a promise the test resolves or rejects (the main-side twin of the renderer one). Slow git children (clone cancel tests) need a hook set in a file pointed to byGIT_CONFIG_GLOBAL:GIT_CONFIG_COUNTis ignored foruploadpack.packObjectsHook, and the hook must be a script file, not an inlinesleep 30 #. - Claude conversations:
src/main/claude/testing/fakeQuery.ts(FakeQuery,setup,useTempRoot,of,canUse,toolOpts,lastRequest,promptInput,flush) is the harness of theconversation.*.test.tsfiles; itsFakeQueryis also the query double of theMcpControllerandPermissionBrokertests (mcpListholds the server rows). A new conversation test goes into the file of its behavior group.
Coverage thresholds
pnpm test:coverage(run by the CIcheckjob) fails below thecoverage.thresholdsofvitest.config.mts: a global floor plus per-glob floors (src/main/**,src/main/claude/**,src/main/lsp/**,src/shared/*.ts,src/renderer/**,src/renderer/src/lib/**). They are the measured values rounded down minus 1 point (CI measures on Linux, which skips the darwin-only tests) and only go up: new code comes with its tests, and when coverage rises, raise the floors in the same change.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

