# How Omarchy's Test Suite Structure Works: A Complete Guide to CLI, Shell, and Acceptance Testing

> Explore Omarchy's test suite structure. Learn how CLI, shell, and acceptance tests are organized and executed for efficient development and quality assurance.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: deep-dive
- Published: 2026-08-20

---

**Omarchy separates its automated tests into two non-graphical suites (CLI and Shell) run via `./test/all`, `./test/cli`, and `./test/shell`, plus a separate graphical acceptance suite executed in a disposable VM.**

Understanding Omarchy's test suite structure is essential for contributors who want to validate changes without breaking the desktop environment. The architecture prioritizes speed and CI compatibility by isolating graphical dependencies while maintaining real-world coverage through VM-based acceptance tests. This guide walks through each suite, the shared testing infrastructure, and how to run tests effectively.

## The Three Test Suites in Omarchy

Omarchy's testing strategy divides verification across three distinct layers. Each layer targets different failure modes and execution environments.

| Suite | Entry Point | Purpose | Location |
|-------|-------------|---------|----------|
| **CLI suite** | `./test/cli` | Command-line router, help output, command metadata, theme-pipeline commands, theme-state migrations | `test/cli` |
| **Shell suite** | `./test/shell` | Individual shell plugins, `bin/` commands, configuration invariants, migrations, compositor-dependent code | `test/shell.d/*-test.sh` |
| **Graphical acceptance suite** | VM-only (not in `./test/all`) | Real-world desktop behavior requiring a live compositor | `test/acceptance.d/` |

The CLI and Shell suites together form the "non-graphical" suite that runs on any headless CI server. The acceptance suite requires a full Wayland desktop session.

## Running the Test Suites

### Full Non-Graphical Suite

Execute both CLI and Shell tests with a single command:

```bash
./test/all

```

This is the primary entry point for continuous integration and pre-commit validation.

### Individual Suite Runners

Run only the CLI tests:

```bash
./test/cli

```

Run only the Shell tests:

```bash
./test/shell

```

The `./test/shell` runner loops over all `test/shell.d/*-test.sh` files, executing each in isolation and summarizing failures at the end.

## Core Testing Infrastructure

### Base Test Library ([`test/shell.d/base-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/base-test.sh))

Every Shell test sources [`test/shell.d/base-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/base-test.sh), which provides the common harness. This file is **not executable on its own**—it is a library meant for inclusion.

The base library handles:

- Repository root discovery
- Temporary `$HOME` setup via `mktemp -d`
- TAP-style assertion functions: `pass`, `fail`, `require_command`

```bash
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

```

This pattern appears at the top of every Shell test file.

### Test Isolation Strategy

Tests run with a fresh temporary `$HOME` while preserving `$OMARCHY_PATH` pointing to the repository checkout. This design ensures:

- Tests exercise the code under development, not the developer's personal configuration
- Side effects are contained and automatically cleaned up
- Parallel test execution remains safe

### Compositor Gating with `require_compositor`

Tests that need a Wayland compositor call `require_compositor` before attempting graphical operations. If no compositor is available, the test automatically skips with a TAP `ok` message:

```bash
require_compositor "menu plugin needs a live compositor"

# ... test logic that talks to Quickshell ...

```

This keeps the Shell suite green on headless CI while allowing the same tests to provide full coverage when run on a developer workstation. The implementation details are documented in [`docs/testing.md`](https://github.com/basecamp/omarchy/blob/main/docs/testing.md), lines 71-85.

## Node.js Unit Tests for Shell Plugins

Shell plugins expose their core logic as CommonJS modules. The `run_node_test` function injects a JavaScript prelude that mirrors Bash TAP output, enabling pure-function tests without a compositor:

```bash
run_node_test <<'JS'
const menu = requireFromRoot('shell/plugins/menu/MenuModel.js')
assertEqual(menu.parseMenuJsonc('{ "items": {} }').length, 0,
            'empty menu yields zero items')
JS

```

This approach is documented in [`docs/testing.md`](https://github.com/basecamp/omarchy/blob/main/docs/testing.md), lines 93-105, and allows testing complex parsing logic without spawning a graphical session.

## Test Fixtures and Shared Data

Common test data lives in `test/shell.d/fixtures/` and is reused across multiple test files. This reduces maintenance burden and ensures consistent test scenarios.

## Failure Handling Behavior

Each individual test file aborts on its first failed assertion (`set -e` behavior). However, the top-level `./test/shell` runner continues to the next test file, collecting all failures for a final summary. This prevents one broken test from masking other failures while still providing clear, immediate feedback per file.

## Key Files Reference

| File | Purpose |
|------|---------|
| `test/all` | Entry point for full non-graphical suite |
| `test/cli` | Standalone CLI test runner (single large bash script) |
| `test/shell` | Shell test runner that loops over `test/shell.d/*-test.sh` |
| [`test/shell.d/base-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/base-test.sh) | Shared test library with TAP assertions and setup |
| [`test/shell.d/menu-plugin-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/menu-plugin-test.sh) | Example Shell test file |
| [`docs/testing.md`](https://github.com/basecamp/omarchy/blob/main/docs/testing.md) | Comprehensive testing framework documentation |
| `test/acceptance.d/` | Graphical acceptance tests for VM execution |

## Summary

- **Omarchy's test suite structure** separates fast, CI-ready tests from graphical acceptance validation
- **CLI suite** (`./test/cli`) validates command-line routing and theme operations
- **Shell suite** (`./test/shell`) tests plugins, commands, and migrations with compositor-gated optional graphics
- **Base library** ([`test/shell.d/base-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/base-test.sh)) provides TAP assertions and isolated `$HOME` setup
- **Node.js bridge** (`run_node_test`) enables pure-logic testing of shell plugins
- **Acceptance suite** runs only in disposable VMs, testing real desktop behavior

## Frequently Asked Questions

### How do I run Omarchy's tests locally?

Execute `./test/all` to run both CLI and Shell suites. For faster iteration, run `./test/cli` or `./test/shell` individually. The acceptance suite requires a VM setup not included in the standard test runner.

### Why does `./test/shell` skip some tests on my machine?

Tests calling `require_compositor` automatically skip when no Wayland compositor is detected. This is intentional—the Shell suite remains useful in headless CI while still providing full coverage on graphical workstations.

### Can I write tests for shell plugins without spawning a desktop?

Yes. Use `run_node_test` to exercise CommonJS modules exported by shell plugins. This tests pure logic (parsing, state management) without requiring `require_compositor` or a live session.

### Where should I add fixtures for new tests?

Place reusable test data in `test/shell.d/fixtures/`. Reference files there using path helpers provided by [`base-test.sh`](https://github.com/basecamp/omarchy/blob/main/base-test.sh) rather than hardcoding paths.