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 $HOME setup via mktemp -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 $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 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →