macos-ui-testing
Background-safe macOS app UI testing using Peekaboo CLI and osascript.
Skill by ducko-im
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/ducko-im-macos-ui-testing
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: design, testing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/macos-ui-testing
What's inside
- Skill name
macos-ui-testing- Description
- Background-safe macOS app UI testing using Peekaboo CLI and osascript. This skill should be used when the user asks to "test the app UI", "check the UI", "interact with the running app", "take a screenshot of the app", "fill in the form", "click the button", "verify the UI state", "automate the app", or wants to automate interaction with a running macOS application without stealing keyboard/mouse focus from the user.
- License
- GPL-3.0
- Instructions
- 189 lines in SKILL.md · view on GitHub
Files (2)
- SKILL.md
- osascript-patterns.md · references/
SKILL.md (first lines)
# macOS UI Testing (Background-Safe)
Run the `/peekaboo` skill first to load full Peekaboo CLI reference. This skill adds background-safe patterns on top.
## Background Safety Rules
Simulated keystrokes (`peekaboo type`, `peekaboo paste`, `peekaboo hotkey`) go to the **frontmost app**, not the target app. Always use background-safe alternatives.
| Action | Background-Safe | Command |
|---|---|---|
| Read UI tree | Yes | `peekaboo see --app APP --json` |
| Screenshot | Yes | `peekaboo see --window-id WID --no-elements` |
| Click element | Yes | `peekaboo click --no-auto-focus --on ELEM --app APP` |
| Set text value | Yes* | `osascript` with `set value of` |
| Read text value | Yes | `osascript` with `get value of` |
| Click button | Yes | `osascript` with `click button` |
| Type keystrokes | **NO** | Lands in frontmost app |
| Paste (Cmd+V) | **NO** | Lands in frontmost app |
| Hotkeys | **NO** | Lands in frontmost app |
*`set value` updates the accessibility layer but **does not trigger SwiftUI `@State` bindings**. Use `keystroke` inside a single osascript block when SwiftUI binding updates are needed (see Multi-Step Interaction Sequences below).
Run the procedures below in the `/lume-vm` VM, so the user's Mac is not disturbed: they run there through `vm.sh gui` against a pushed app bundle, and Ducko's scripts through `vm.sh ui`. Run them on the user's Mac only when the user asks for that, and even then tell the user before a step that moves the pointer, brings a window to the front or switches the system appearance.
## Check for a Locked Screen
While the session screen is locked, window accessibility elements report the role `AXApplication`, controls inside windows can't be resolved, and window captures fail (`screencapture -l` reports "could not create image from window"). Read the lock state before starting the workflow:
```bash
ioreg -n Root -d1 -a | grep -A1 CGSSessionScreenIsLocked
```
`<true/>` on the line after the key means locked. No output means unlocked, because the key is absent then. An `ioreg` error, such as "can't open file" inside a sandbox, leaves the lock state unknown. When the screen is locked, record the GUI check as inconclusive instead of working around it.
## A Glitch in an App's First Frames
In the observed launch, the first accessibility answer came about half a second after the process started, so a layout glitch in an app's first frames is over before the first read. A screen recording is no substitute (see Step 4). Log the geometry from inside the app instead: put a logging probe into a scratch copy of the code and launch that. To check a fix, put the same probe into scratch copies of the unfixed and the fixed code and compare the logs.
## Workflow
### Step 1: Launch the App
```bash
# For SwiftPM-built apps:
swift run AppName &>/dev/null &
APP_PID=$!
sleep 3
# For installed apps:
open -a "AppName"
```
### Step 2: Verify the App Is Running
```bash
peekaboo app list | grep -i AppName
```
### Step 3: Read the UI Tree
```bash
peekaboo see --app AppName --json
```
Parse the JSON output to find element IDs (`elem_N`), roles, and labels.
### Step 4: Take a Screenshot
Resolve the window ID first, then capture by ID:
```bash
peekaboo window list --app AppName
peekaboo see --window-id WID --no-elements --path /tmp/screenshot.png
```
Capture by window ID only. A screen recording, or a capture of the whole screen or a region of it, holds everything else the user has open, and shows whatever is in front, which may not be the app. `screencapture -l WID` has captured a window correctly while it was on another Space.
In top-level code of a Swift script that captures with ScreenCaptureKit, annotate the result (`let image: CGImage = try await SCScreenshotManager.captureImage(...)`), or the completion-handler overload is chosen and the result is `Void`. Touch `NSApplication.shared` before the first capture, or it asserts in `CGS_REQUIRE_INIT`.
To cut a region out of a capture, read it with `CGImageSource`, crop it with `CGImage.cropping(to:)` and write it with `CGImageDestination`. The rectangle is in pixels, and one that runs past the image is clamped without an error, so check the result's size. `sips -c H W --cropOffset 0 0` crops the center rather than the top left, and sips cannot write its temporary file inside the Bash sandbox.
### Step 5: Interact with Elements
**Click an element** (accessibility API, no focus needed):
```bash
peekaboo click --no-auto-focus --on elem_4 --app AppName
```
**Set a text field value** (background-safe, but does NOT trigger SwiftUI bindings):
```bash
osascript -e 'tell application "System Events" to set value of text field 1 of group 1 of window 1 of process "AppName" to "text"'
```
**Read a text field value**:
```bash
osascript -e 'tell application "System Events" to get value of text field 1 of group 1 of window 1 of process "AppName"'
```
**Click a button by name**:
```bash
osascript -e 'tell application "System Events" to click button "Submit" of group 1 of window 1 of process "AppName"'
```
> **Synthetic coordinate clicks don't drive SwiftUI gestures.** AppleScript `click at {x,y}` and `peekaboo click --at X,Y` do **not** trigger `.onTapGesture`/`.simultaneousGesture` or `List(selection:)` selection. A tap gesture fires only for a real user click, and a `Button` also fires on an accessibility **`AXPress`** (`peekaboo click --on ELEM` from `see --json`). An AXPress on a list row, which a System Events `click` on it is, leaves the selection unchanged too: set `AXSelected` to true on the row's enclosing `AXRow` instead, which does select it (observed on macOS 27). UI relying on tap gestures **can't be confirmed by synthetic clicks** — treat such a result as a signal, not proof, and hand the app to the user when only real clicks suffice.
>
> Peekaboo targets apps by **display name** (`--app Ducko`), not the executable name (`DuckoApp`).
>
> Peekaboo 4 replaced `click --coords` with `--at X,Y`. In the background, `--at` coordinates are relative to the window of a `--snapshot` from a fresh `see --window-id WID`. `--foreground` focuses the target app instead, so use it only in the `/lume-vm` VM.
### Step 6: Verify Results
Take another screenshot or read values back to confirm the interaction succeeded.
### Step 7: Stop the AppFrom ducko-im/ducko, GPL-3.0.
More from ducko-im/ducko
- 0.629score
writing-for-interfaces
ducko-im
Use when someone asks to write, rewrite, review, or improve text that appears inside a product or interface.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
accessibility
ducko-im
Apply platform accessibility best practices to SwiftUI, UIKit, and AppKit code.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-testing
ducko-im
Expert guidance on Swift Testing for writing, reviewing, migrating, and debugging tests.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swiftdata
ducko-im
Expert guidance for designing, writing, reviewing, and debugging SwiftData persistence in Swift and SwiftUI apps.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swiftui
ducko-im
Write, review, or improve SwiftUI code across state management, view composition, performance, modern APIs, navigation, animation, accessibility, Liquid Glass…
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swiftui-performance-audit
ducko-im
Audit and improve SwiftUI runtime performance from code review and architecture.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
macos-spm-app-packaging
ducko-im
Scaffold, build, and package SwiftPM-based macOS apps without an Xcode project.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
release
ducko-im
Cut a Ducko release: update CHANGELOG.md on main, then push the X.Y.Z tag that triggers the release.yml CI workflow (build, sign, notarize, DMG, Sparkle…
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-concurrency
ducko-im
Expert guidance on Swift Concurrency correctness, modern API usage, and async/await pitfalls in Swift 6.2+.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-language
ducko-im
Write, review, or improve Swift APIs and value-display formatting.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-security
ducko-im
Use when working with iOS/macOS Keychain Services (SecItem queries, kSecClass, OSStatus errors), biometric authentication (LAContext, Face ID, Touch ID)…
SkillAFreshness: A★ 7Claude Code +9 - 0.625score
lume-vm
ducko-im
Run Ducko UI work inside a disposable Lume macOS VM instead of on the host desktop: start and stop the VM, run commands in its logged-in GUI session…
SkillAFreshness: A★ 7Claude Code +9
Signals
- Stars
- 7
- Forks
- 0
- Open issues
- 4
- Last commit (this folder)
- Oct 22, 2026 (6 days ago)
- Last release
- Oct 23, 2026 (5 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- GPL-3.0
- Repository
- github.com/ducko-im/ducko
- Website
- ducko.im
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from ducko-im
- 0.629score
swift-security
ducko-im
Use when working with iOS/macOS Keychain Services (SecItem queries, kSecClass, OSStatus errors), biometric authentication (LAContext, Face ID, Touch ID)…
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-language
ducko-im
Write, review, or improve Swift APIs and value-display formatting.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swift-concurrency
ducko-im
Expert guidance on Swift Concurrency correctness, modern API usage, and async/await pitfalls in Swift 6.2+.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
macos-spm-app-packaging
ducko-im
Scaffold, build, and package SwiftPM-based macOS apps without an Xcode project.
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
release
ducko-im
Cut a Ducko release: update CHANGELOG.md on main, then push the X.Y.Z tag that triggers the release.yml CI workflow (build, sign, notarize, DMG, Sparkle…
SkillAFreshness: A★ 7Claude Code +9 - 0.629score
swiftdata
ducko-im
Expert guidance for designing, writing, reviewing, and debugging SwiftData persistence in Swift and SwiftUI apps.
SkillAFreshness: A★ 7Claude Code +9
Cite this page
macos-ui-testing skill. Agent Mart. https://agentmart.dev/skills/ducko-im-macos-ui-testing (last updated 2026-10-24)