How Omarchy's Test Suite Structure Works: A Complete Guide to CLI, Shell, and Acceptance Testing
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:
./test/all
This is the primary entry point for continuous integration and pre-commit validation.
Individual Suite Runners
Run only the CLI tests:
./test/cli
Run only the Shell tests:
./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)
Every Shell test sources 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
$HOMEsetup viamktemp -d - TAP-style assertion functions:
pass,fail,require_command
#!/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:
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, 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:
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, 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 |
Shared test library with TAP assertions and setup |
test/shell.d/menu-plugin-test.sh |
Example Shell test file |
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) provides TAP assertions and isolated$HOMEsetup - 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 rather than hardcoding paths.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →