# Test Mac App

Test a macOS app at three levels, from Swift Testing unit tests and an in-app self-test...

- Repository: https://github.com/daveebbelaar/bama (1284 stars)
- Category: Testing
- License: MIT
- Last updated: 2026-11-01
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add daveebbelaar/bama --skill test-mac-app -g -a claude-code -y`
Codex: `npx skills add daveebbelaar/bama --skill test-mac-app -g -a codex -y`
Cursor: `npx skills add daveebbelaar/bama --skill test-mac-app -g -a cursor -y`
Gemini CLI: `npx skills add daveebbelaar/bama --skill test-mac-app -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add daveebbelaar/bama`

## Description

Test a macOS app at three levels, from Swift Testing unit tests and an in-app self-test to a debug bridge that lets an agent drive the running app and read its state, plus computer use for real clicks. Use when adding logic, fixing a bug, before a release, or when the user asks for tests, end-to-end checks, or "make sure it works".

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Test a Mac app

Three levels, cheapest first. Each new story gets the cheapest level that
can prove its acceptance criteria. A bug fix starts with a test that fails.

| Level | Proves | Speed |
| --- | --- | --- |
| Unit tests (Swift Testing) | Rules, parsing, data | Seconds |
| Self-test (`--selftest`) | The app's real functions on a test store | Seconds |
| Key events and menu audit (inside the self-test) | Keyboard flows and every shortcut, without the screen | Seconds |
| Debug bridge + computer use | Global shortcuts, panels, permissions, other apps | Minutes |

Snapshots (`build-and-verify` skill) prove how it looks. This skill proves
how it behaves.

## 1. Unit tests

Logic lives in a library target (`<App>Kit`) with no SwiftUI, so tests run
without the app.

```swift
import Testing
@testable import AppKitCore

@Test("Natural dates", arguments: [("tomorrow 9am", 1, 9), ("next fri", 5, nil)])
func parses(_ phrase: String, dayOffset: Int, hour: Int?) throws {
    let result = try #require(DateParser.parse(phrase, now: .fixed))
    #expect(result.dayOffset == dayOffset)
    #expect(result.hour == hour)
}
```

```bash
swift test --scratch-path ~/Library/Developer/<App>/test-build
```

- Fix the clock (`now:` parameter) and the store path in every test.
- A parser that removes text from what the user typed needs at least 20
  negative rows: real titles that contain date or time words ("Watch
  Friday Night Lights"). Add consistency rows for related phrases ("next
  week" and "next friday"), and a tested way to undo a parsed part.
- **Table tests** for anything with many cases. Keep the table in
  `docs/research/<topic>.md` too, and add a row there before changing
  behavior. A date parser easily needs 100 rows.

## 2. Self-test

A launch flag that runs the same functions the UI calls, prints PASS or FAIL
per step, and exits 0 or 1:

The rest: https://github.com/daveebbelaar/bama/blob/HEAD/skills/ship/test-mac-app/SKILL.md
