Expand graphical acceptance coverage

This commit is contained in:
David Heinemeier Hansson
2026-07-22 17:31:14 -07:00
parent 83d20b07d1
commit 1de4c89030
8 changed files with 460 additions and 31 deletions
+23 -2
View File
@@ -22,6 +22,16 @@ export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNT
export OMARCHY_PATH="${OMARCHY_PATH:-$ROOT}" export OMARCHY_PATH="${OMARCHY_PATH:-$ROOT}"
export PATH="$OMARCHY_PATH/bin:$PATH" export PATH="$OMARCHY_PATH/bin:$PATH"
if [[ -z ${DISPLAY:-} ]]; then
display=$(systemctl --user show-environment | sed -n 's/^DISPLAY=//p' | head -1)
export DISPLAY="${display:-:0}"
fi
if [[ -z ${LANG:-} ]]; then
locale_name=$(systemctl --user show-environment | sed -n 's/^LANG=//p' | head -1)
export LANG="${locale_name:-C.UTF-8}"
fi
# First boot may still be settling; wait for Hyprland to come up. # First boot may still be settling; wait for Hyprland to come up.
boot_deadline=$((SECONDS + ${OMARCHY_ACCEPTANCE_BOOT_TIMEOUT:-300})) boot_deadline=$((SECONDS + ${OMARCHY_ACCEPTANCE_BOOT_TIMEOUT:-300}))
@@ -66,9 +76,20 @@ if (( ${#tests[@]} == 0 )); then
exit 1 exit 1
fi fi
status=0
suite_started=$SECONDS
for test in "${tests[@]}"; do for test in "${tests[@]}"; do
test_started=$SECONDS
printf '==> %s\n' "${test#$ROOT/}" printf '==> %s\n' "${test#$ROOT/}"
bash "$test" if ! timeout "${OMARCHY_ACCEPTANCE_TEST_TIMEOUT:-420}" bash "$test"; then
status=1
fi
printf ' completed in %ss\n' "$((SECONDS - test_started))"
done done
printf 'ok - acceptance suite passed (artifacts in %s)\n' "$OMARCHY_ACCEPTANCE_DIR" if ((status != 0)); then
printf 'not ok - acceptance suite failed in %ss (artifacts in %s)\n' "$((SECONDS - suite_started))" "$OMARCHY_ACCEPTANCE_DIR" >&2
exit $status
fi
printf 'ok - acceptance suite passed in %ss (artifacts in %s)\n' "$((SECONDS - suite_started))" "$OMARCHY_ACCEPTANCE_DIR"
+15 -11
View File
@@ -5,7 +5,7 @@ set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
launch_and_verify() { launch_and_verify() {
local name="$1" command="$2" class="$3" local name="$1" command="$2" class="$3" timeout="${4:-45}"
# A pre-existing window makes the test ambiguous (and closing it would be # A pre-existing window makes the test ambiguous (and closing it would be
# hostile on a dev machine) — acceptance runs expect a fresh session. # hostile on a dev machine) — acceptance runs expect a fresh session.
@@ -14,9 +14,9 @@ launch_and_verify() {
fi fi
launch_app "$command" launch_app "$command"
wait_until "$name opens a window" 90 window_present "$class" wait_until "$name opens a window" "$timeout" window_present "$class"
sleep 1 sleep 1
screenshot "app-$name" screenshot "success-app-$name"
local deadline=$((SECONDS + 30)) local deadline=$((SECONDS + 30))
while window_present "$class" >/dev/null 2>&1; do while window_present "$class" >/dev/null 2>&1; do
@@ -31,15 +31,19 @@ launch_and_verify() {
pass "$name window closes" pass "$name window closes"
} }
# name|command|window class regex # Keep launch coverage to the primary daily-use paths. The system acceptance
# test separately verifies the complete core package manifest.
# name|command|window class regex|launch timeout
apps='terminal|foot|^foot$ apps='terminal|foot|^foot$
browser|chromium --new-window|(?i)chromium browser|chromium --new-window|(?i)chromium
files|nautilus --new-window|org.gnome.Nautilus neovim|xdg-terminal-exec --app-id=org.omarchy.nvim nvim|org.omarchy.nvim
calculator|gnome-calculator|org.gnome.Calculator writer|omawrite|(?i)omawrite'
notes|obsidian|(?i)obsidian
office|libreoffice|(?i)(soffice|libreoffice)
pdf-viewer|evince|(?i)evince'
while IFS='|' read -r name command class; do status=0
launch_and_verify "$name" "$command" "$class" while IFS='|' read -r name command class timeout; do
if ! (launch_and_verify "$name" "$command" "$class" "$timeout"); then
status=1
fi
done <<<"$apps" done <<<"$apps"
exit $status
+20 -2
View File
@@ -17,15 +17,33 @@ pass() {
fail() { fail() {
local description="$1" local description="$1"
local detail="${2:-}" local detail="${2:-}"
local step=${description,,}
step=${step// /-}
step=${step//[^a-z0-9-]/}
[[ -n $detail ]] && printf '%s\n' "$detail" >&2 [[ -n $detail ]] && printf '%s\n' "$detail" >&2
screenshot "failure-$(date +%s)" screenshot "failure-$step"
printf 'not ok - %s\n' "$description" >&2 printf 'not ok - %s\n' "$description" >&2
exit 1 exit 1
} }
screenshot() { screenshot() {
grim "$ARTIFACTS/$1.png" 2>/dev/null || true timeout 10 grim "$ARTIFACTS/$1.png" 2>/dev/null || true
}
screen_contains() {
local text="$1"
local snapshot="/tmp/omarchy-acceptance-ocr-$$.png"
if ! timeout 10 grim "$snapshot" 2>/dev/null; then
rm -f "$snapshot"
return 1
fi
tesseract "$snapshot" stdout --psm 11 2>/dev/null | grep -Fqi -- "$text"
local status=$?
rm -f "$snapshot"
return $status
} }
# Poll a command until it succeeds; screenshot and fail on timeout. # Poll a command until it succeeds; screenshot and fail on timeout.
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
CONFIG_FILE="$HOME/.config/omarchy/shell.json"
DEFAULTS_FILE="$OMARCHY_PATH/config/omarchy/shell.json"
config_backup=$(mktemp)
config_existed=0
if [[ -f $CONFIG_FILE ]]; then
cp "$CONFIG_FILE" "$config_backup"
config_existed=1
fi
restore_bar_config() {
omarchy-shell shell hide omarchy.menu >/dev/null 2>&1 || true
if ((config_existed)); then
mkdir -p "$(dirname "$CONFIG_FILE")"
cp "$config_backup" "$CONFIG_FILE"
else
rm -f "$CONFIG_FILE"
fi
omarchy-shell shell reloadConfig >/dev/null 2>&1 || true
rm -f "$config_backup"
}
trap restore_bar_config EXIT
bar_is_vertical() {
local width height
read -r width height < <(hyprctl -j layers | jq -r '
[.. | objects | select(.namespace? == "omarchy-bar")][0]
| [.w, .h] | @tsv
')
[[ $width =~ ^[0-9]+$ && $height =~ ^[0-9]+$ ]] && ((width < height))
}
bar_is_horizontal() {
local width height
read -r width height < <(hyprctl -j layers | jq -r '
[.. | objects | select(.namespace? == "omarchy-bar")][0]
| [.w, .h] | @tsv
')
[[ $width =~ ^[0-9]+$ && $height =~ ^[0-9]+$ ]] && ((width > height))
}
bar_position_is() {
local expected="$1"
[[ $(jq -r '.bar.position // "top"' "$CONFIG_FILE") == $expected ]]
}
source_file=$DEFAULTS_FILE
((config_existed)) && source_file=$CONFIG_FILE
original_position=$(jq -r '.bar.position // "top"' "$source_file")
omarchy-shell shell summon omarchy.menu '{"menu":"root"}' >/dev/null
wait_until "root menu opens" 15 layer_present "omarchy-menu"
wait_until "root menu content is visible" 15 screen_contains "Apps"
screenshot "success-menu-01-root"
wtype -k Down -k Down -k Down
sleep 1
screenshot "success-menu-02-style-selected"
wtype -k Return
wait_until "style submenu is visible" 15 screen_contains "Theme"
screenshot "success-menu-03-style-submenu"
wtype -k Down -k Down -k Down -k Return
sleep 1
screenshot "success-menu-04-menu-bar-submenu"
wtype -k Return
sleep 1
screenshot "success-menu-05-position-submenu"
wtype -k Down -k Down -k Return
wait_until "menu bar position changes to left" 20 bar_position_is "left"
wait_until "menu bar becomes vertical" 20 bar_is_vertical
wait_until "menu closes after selecting a position" 15 layer_absent "omarchy-menu"
screenshot "success-menu-06-bar-left"
if ((config_existed)); then
cp "$config_backup" "$CONFIG_FILE"
else
rm -f "$CONFIG_FILE"
fi
omarchy-shell shell reloadConfig >/dev/null
if [[ $original_position == "left" || $original_position == "right" ]]; then
wait_until "menu bar restores its original vertical position" 20 bar_is_vertical
else
wait_until "menu bar restores its original horizontal position" 20 bar_is_horizontal
fi
screenshot "success-menu-07-bar-restored"
trap - EXIT
restore_bar_config
+103
View File
@@ -0,0 +1,103 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
WEATHER_FILE="$HOME/.local/state/omarchy/settings/weather.json"
weather_backup=$(mktemp)
weather_existed=0
if [[ -f $WEATHER_FILE ]]; then
cp "$WEATHER_FILE" "$weather_backup"
weather_existed=1
fi
hide_panels() {
local plugin
for plugin in omarchy.weather omarchy.bluetooth omarchy.network omarchy.audio omarchy.monitor omarchy.power; do
omarchy-shell shell hide "$plugin" >/dev/null 2>&1 || true
done
}
restore_weather() {
hide_panels
if ((weather_existed)); then
mkdir -p "$(dirname "$WEATHER_FILE")"
cp "$weather_backup" "$WEATHER_FILE"
else
rm -f "$WEATHER_FILE"
fi
rm -f "$weather_backup"
}
trap restore_weather EXIT
open_and_capture_panel() {
local name="$1" plugin="$2"
omarchy-shell shell summon "$plugin" >/dev/null
wait_until "$name panel opens" 15 layer_present "omarchy-keyboard-panel"
sleep 1
screenshot "success-panel-$name"
omarchy-shell shell hide "$plugin" >/dev/null
wait_until "$name panel closes" 15 layer_absent "omarchy-keyboard-panel"
}
# Give weather deterministic coordinates so this test exercises the real
# Open-Meteo forecast instead of IP geolocation through wttr.in.
omarchy-weather-location --set "San Francisco" "37.7749,-122.4194"
omarchy-shell shell summon omarchy.weather >/dev/null
wait_until "weather panel opens" 15 layer_present "omarchy-keyboard-panel"
wait_until "weather location is visible" 30 screen_contains "SAN FRANCISCO"
wait_until "weather details are visible" 30 screen_contains "WIND"
screenshot "success-panel-weather"
omarchy-shell shell hide omarchy.weather >/dev/null
wait_until "weather panel closes" 15 layer_absent "omarchy-keyboard-panel"
status=0
panels='bluetooth|omarchy.bluetooth
network|omarchy.network
audio|omarchy.audio
monitor|omarchy.monitor'
while IFS='|' read -r name plugin; do
if ! (trap - EXIT; open_and_capture_panel "$name" "$plugin"); then
status=1
hide_panels
wait_until "$name failed panel is dismissed" 15 layer_absent "omarchy-keyboard-panel"
fi
done <<<"$panels"
# The power widget intentionally disappears on desktops and VMs without a
# battery. Exercise it on laptops, and verify that hardware-less sessions take
# the supported no-panel path instead of treating that as a shell failure.
if upower -e | grep -q '/battery_'; then
if ! (trap - EXIT; open_and_capture_panel "power" "omarchy.power"); then
status=1
hide_panels
wait_until "power failed panel is dismissed" 15 layer_absent "omarchy-keyboard-panel"
fi
else
pass "power panel is hidden without battery hardware"
screenshot "success-panel-power-unavailable"
fi
# The common panel keyboard contract uses Tab to move to the next bar panel.
omarchy-shell shell summon omarchy.bluetooth >/dev/null
wait_until "panel keyboard navigation starts on bluetooth" 15 screen_contains "Bluetooth"
screenshot "success-panel-navigation-01-bluetooth"
wtype -k Tab
sleep 2
wait_until "Tab keeps a shell panel open" 15 layer_present "omarchy-keyboard-panel"
screenshot "success-panel-navigation-02-next"
hide_panels
wait_until "keyboard-navigated panel closes" 15 layer_absent "omarchy-keyboard-panel"
trap - EXIT
restore_weather
exit $status
+6 -2
View File
@@ -14,7 +14,11 @@ wait_until "omarchy-shell responds to ping" 60 omarchy-shell shell ping
# Core shell plugins are loaded # Core shell plugins are loaded
plugins=$(omarchy-shell shell listPlugins) plugins=$(omarchy-shell shell listPlugins)
for plugin in omarchy.bar omarchy.launcher omarchy.emojis omarchy.menu omarchy.notifications; do for plugin in \
omarchy.audio omarchy.background omarchy.bar omarchy.bluetooth \
omarchy.clipboard omarchy.emojis omarchy.launcher omarchy.menu \
omarchy.monitor omarchy.network omarchy.notifications omarchy.power \
omarchy.reminders omarchy.weather; do
[[ $plugins == *"$plugin"* ]] || fail "shell plugin is loaded: $plugin" "loaded plugins: $plugins" [[ $plugins == *"$plugin"* ]] || fail "shell plugin is loaded: $plugin" "loaded plugins: $plugins"
pass "shell plugin is loaded: $plugin" pass "shell plugin is loaded: $plugin"
done done
@@ -53,4 +57,4 @@ if [[ -n $failed_user ]]; then
fi fi
pass "no failed user units" pass "no failed user units"
screenshot "desktop" screenshot "success-desktop"
+82 -14
View File
@@ -15,34 +15,102 @@ open_and_close() {
wait_until "$name opens" 15 layer_present "$namespace" wait_until "$name opens" 15 layer_present "$namespace"
sleep 1 sleep 1
screenshot "$name-open" screenshot "success-$name-open"
omarchy-shell shell hide "$plugin" >/dev/null omarchy-shell shell hide "$plugin" >/dev/null
wait_until "$name closes" 15 layer_absent "$namespace" wait_until "$name closes" 15 layer_absent "$namespace"
} }
# Overlays open and close on IPC command # Search and select an emoji. The host harness separately proves the shortcut
open_and_close "emoji-picker" omarchy.emojis omarchy-emojis # with a QMP hardware key chord, while this test focuses on UI behavior.
open_and_close "menu" omarchy.menu omarchy-menu '{"menu":"root"}' omarchy-shell shell summon omarchy.emojis >/dev/null
open_and_close "clipboard" omarchy.clipboard omarchy-clipboard wait_until "emoji picker opens" 15 layer_present "omarchy-emojis"
wtype "rocket"
sleep 1
screenshot "success-emoji-picker-search"
wtype -k Return
wait_until "emoji picker selection closes" 15 layer_absent "omarchy-emojis"
# The launcher does the full loop: open, search by typing, launch the top hit # Seed two clipboard entries, search for the older one, and copy it back out.
if window_present "org.gnome.Calculator" >/dev/null 2>&1; then clipboard_token="Omarchy acceptance clipboard $(date +%s)"
fail "launcher test starts with no calculator window" "a calculator window is already open" printf '%s' "$clipboard_token" | wl-copy
wait_until "clipboard history captures test text" 15 grep -Fq "$clipboard_token" "$HOME/.local/state/omarchy/clipboard-history.json"
printf '%s' "clipboard decoy" | wl-copy
sleep 1
omarchy-shell shell summon omarchy.clipboard >/dev/null
wait_until "clipboard opens" 15 layer_present "omarchy-clipboard"
wtype "$clipboard_token"
wait_until "clipboard search finds test text" 15 screen_contains "Omarchy acceptance clipboard"
screenshot "success-clipboard-search"
wtype -M shift -k Return -m shift
wait_until "clipboard selection closes" 15 layer_absent "omarchy-clipboard"
wait_until "clipboard selection restores test text" 15 bash -c '[[ $(wl-paste --no-newline) == "$1" ]]' _ "$clipboard_token"
# Exercise the system branch without invoking any destructive action.
omarchy-shell shell summon omarchy.menu '{"menu":"system"}' >/dev/null
wait_until "system menu opens" 15 layer_present "omarchy-menu"
wait_until "system menu content is visible" 15 screen_contains "Shutdown"
screenshot "success-system-menu"
wtype -k Escape
wait_until "system menu closes" 15 layer_absent "omarchy-menu"
# Preview both visual selectors and cancel without changing user state. These
# cover thumbnail generation, the image-grid overlay, and current selection.
launch_app "omarchy-theme-bg-switcher"
wait_until "background selector opens" 30 layer_present "omarchy-image-selector"
sleep 1
screenshot "success-background-selector"
wtype -k Escape
wait_until "background selector closes" 15 layer_absent "omarchy-image-selector"
launch_app "omarchy-theme-switcher"
wait_until "theme selector opens" 30 layer_present "omarchy-image-selector"
sleep 1
screenshot "success-theme-selector"
wtype -k Escape
wait_until "theme selector closes" 15 layer_absent "omarchy-image-selector"
# Walk the reminder flow through each input screen, but dismiss before it
# schedules a real timer in the test user's session.
omarchy-shell shell summon omarchy.reminders >/dev/null
wait_until "reminder flow opens" 15 layer_present "omarchy-reminders"
screenshot "success-reminder-01-minutes-prompt"
wtype "5"
sleep 1
screenshot "success-reminder-02-minutes-entered"
wtype -k Return
wait_until "reminder message prompt opens" 15 screen_contains "Reminder message"
screenshot "success-reminder-03-message-prompt"
wtype -k Escape
wait_until "reminder flow closes" 15 layer_absent "omarchy-reminders"
# Render a real shell notification and clear it through the notification IPC.
omarchy-shell notifications dismissAll >/dev/null
omarchy-notification-send "Acceptance notification" "Shell notification rendering" --expire-time=15000
wait_until "notification popup opens" 15 layer_present "omarchy-notifications"
wait_until "notification content is visible" 15 screen_contains "Acceptance notification"
screenshot "success-notification-popup"
omarchy-shell notifications dismissAll >/dev/null
wait_until "notification popup closes" 15 layer_absent "omarchy-notifications"
# The launcher does the full loop: open, search by typing, launch the top hit.
if window_present "(?i)omawrite" >/dev/null 2>&1; then
fail "launcher test starts with no Omawrite window" "an Omawrite window is already open"
fi fi
omarchy-shell shell summon omarchy.launcher >/dev/null omarchy-shell shell summon omarchy.launcher >/dev/null
wait_until "launcher opens" 15 layer_present "omarchy-launcher" wait_until "launcher opens" 15 layer_present "omarchy-launcher"
sleep 1 sleep 1
screenshot "launcher-open" screenshot "success-launcher-open"
wtype "calculator" wtype "omawrite"
sleep 1 sleep 1
screenshot "launcher-search" screenshot "success-launcher-search"
wtype -k Return wtype -k Return
wait_until "launcher launches the top search hit" 60 window_present "org.gnome.Calculator" wait_until "launcher launches the top search hit" 60 window_present "(?i)omawrite"
wait_until "launcher closes after launching" 15 layer_absent "omarchy-launcher" wait_until "launcher closes after launching" 15 layer_absent "omarchy-launcher"
close_windows "org.gnome.Calculator" close_windows "(?i)omawrite"
wait_until "calculator window closes" 30 window_absent "org.gnome.Calculator" wait_until "Omawrite window closes" 30 window_absent "(?i)omawrite"
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
status=0
verify_core_packages() {
local package
local -a missing=()
while IFS= read -r package; do
[[ -z $package || $package == \#* ]] && continue
pacman -Q "$package" >/dev/null 2>&1 || missing+=("$package")
done <"$OMARCHY_PATH/install/omarchy-base.packages"
(( ${#missing[@]} == 0 )) || fail "all Omarchy core packages are installed" "missing packages: ${missing[*]}"
pass "all Omarchy core packages are installed (${#missing[@]} missing)"
}
verify_defaults() {
[[ $(omarchy-default-browser) == "chromium" ]] || fail "Chromium is the default browser"
pass "Chromium is the default browser"
[[ $(omarchy-default-terminal) == "foot" ]] || fail "Foot is the default terminal"
pass "Foot is the default terminal"
[[ $(omarchy-default-editor) == "nvim" ]] || fail "Neovim is the default editor"
pass "Neovim is the default editor"
[[ $(omarchy-theme-current) != "Unknown" ]] || fail "a current theme is configured"
pass "a current theme is configured"
[[ $(omarchy-theme-bg-current) != "Unknown" ]] || fail "a current background is configured"
pass "a current background is configured"
[[ -n $(omarchy-font-current) ]] || fail "a monospace font is configured"
pass "a monospace font is configured"
[[ $(xdg-mime query default x-scheme-handler/http) == "chromium.desktop" ]] || fail "HTTP MIME handling uses Chromium"
[[ $(xdg-mime query default inode/directory) == "org.gnome.Nautilus.desktop" ]] || fail "directory MIME handling uses Nautilus"
pass "desktop MIME handlers are configured"
}
verify_services() {
local unit
for unit in \
avahi-daemon.service cups.service cups-browsed.service docker.socket \
NetworkManager.service power-profiles-daemon.service sddm.service \
systemd-resolved.service ufw.service; do
systemctl is-enabled --quiet "$unit" || fail "core system services are enabled" "$unit is not enabled"
done
pass "core system services are enabled"
for unit in NetworkManager.service systemd-resolved.service ufw.service; do
systemctl is-active --quiet "$unit" || fail "critical system services are running" "$unit is not active"
done
pass "critical system services are running"
systemctl --user is-active --quiet pipewire.service pipewire-pulse.service wireplumber.service ||
fail "user audio services are running"
pass "user audio services are running"
}
verify_runtime_tools() {
timeout 20 docker info >/dev/null 2>&1 || fail "Docker is usable by the desktop user"
pass "Docker is usable by the desktop user"
nvim --headless '+qa' >/dev/null 2>&1 || fail "Neovim starts headlessly"
pass "Neovim starts headlessly"
timeout 10 fastfetch --pipe false >/dev/null 2>&1 || fail "Fastfetch can read system information"
pass "Fastfetch can read system information"
git --version >/dev/null || fail "Git is installed and runnable"
tmux -V >/dev/null || fail "Tmux is installed and runnable"
mise --version >/dev/null || fail "Mise is installed and runnable"
pass "core terminal tools are runnable"
}
verify_user_setup() {
local directory
for directory in DESKTOP DOCUMENTS DOWNLOAD PICTURES; do
[[ -d $(xdg-user-dir "$directory") ]] || fail "XDG user directories exist" "$directory is missing"
done
pass "XDG user directories exist"
[[ -e $HOME/.local/state/omarchy/current/theme ]] || fail "current theme state exists"
[[ -e $HOME/.local/state/omarchy/current/background ]] || fail "current background state exists"
[[ -s $HOME/.config/omarchy/shell.json ]] || fail "shell configuration exists"
jq empty "$HOME/.config/omarchy/shell.json" || fail "shell configuration is valid JSON"
pass "Omarchy user state and shell configuration exist"
}
for check in verify_core_packages verify_defaults verify_services verify_runtime_tools verify_user_setup; do
if ! ("$check"); then
status=1
fi
done
exit $status