Skip to content

feat: add Linux desktop automation support via AT-SPI2 #5

feat: add Linux desktop automation support via AT-SPI2

feat: add Linux desktop automation support via AT-SPI2 #5

Workflow file for this run

name: Linux
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
smoke-linux:
name: Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 30
env:
# Force X11 mode (Xvfb) — no Wayland on CI.
XDG_SESSION_TYPE: x11
DISPLAY: ":99"
# Headless GTK: avoid dconf issues, enable accessibility bridge.
GSETTINGS_BACKEND: memory
NO_AT_BRIDGE: "0"
GTK_A11Y: atspi
GTK_MODULES: "gail:atk-bridge"
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Install native build deps BEFORE pnpm install so node-gtk can compile.
- name: Install Linux desktop dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
build-essential \
gobject-introspection \
libgirepository1.0-dev \
libglib2.0-dev \
libcairo2-dev \
at-spi2-core \
libatk-adaptor \
gir1.2-atspi-2.0 \
xvfb \
xdotool \
scrot \
gnome-calculator \
dbus-x11 \
wmctrl
- name: Setup toolchain
uses: ./.github/actions/setup-node-pnpm
- name: Build node-gtk native module
run: |
# node-gtk uses node-pre-gyp. The S3-hosted prebuilt likely doesn't
# exist for this Node ABI (v127 / Node 22), and pnpm install silently
# skips failed optional dep builds. Run node-pre-gyp directly inside
# the package to force a from-source build.
NODE_GTK_DIR=$(ls -d node_modules/.pnpm/node-gtk@*/node_modules/node-gtk)
echo "node-gtk at: $NODE_GTK_DIR"
cd "$NODE_GTK_DIR"
npx @mapbox/node-pre-gyp install --fallback-to-build --update-binary 2>&1
ls -la lib/binding/*/node_gtk.node
- name: Start Xvfb and D-Bus
run: |
# Start virtual framebuffer (1280x1024, 24-bit color)
Xvfb :99 -screen 0 1280x1024x24 &
sleep 1
# Start a D-Bus session and export its env vars for subsequent steps.
# dbus-launch forks a persistent daemon, so it survives the step.
eval "$(dbus-launch --sh-syntax)"
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" >> "$GITHUB_ENV"
echo "DBUS_SESSION_BUS_PID=$DBUS_SESSION_BUS_PID" >> "$GITHUB_ENV"
- name: Start AT-SPI2 registry
run: |
# The registry must start AFTER DBUS_SESSION_BUS_ADDRESS is available
# (it was written to GITHUB_ENV in the previous step).
ATSPI_REG=$(find /usr -name at-spi2-registryd -type f 2>/dev/null | head -1)
if [ -n "$ATSPI_REG" ]; then
"$ATSPI_REG" &
sleep 2
echo "AT-SPI2 registry started: $ATSPI_REG"
else
echo "::warning::at-spi2-registryd not found — accessibility tree will be empty"
fi
- name: Verify environment
run: |
echo "=== Display ==="
xdotool getdisplaygeometry || echo "Display check failed"
echo "=== D-Bus ==="
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS"
dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames 2>&1 | head -20
echo "=== node-gtk ==="
node -e "import('node-gtk').then(gi => { const Atspi = gi.require('Atspi', '2.0'); console.log('node-gtk + AT-SPI2 GI bindings OK'); process.exit(0); }).catch(e => { console.error('node-gtk FAILED:', e.message); process.exit(1); })"
echo "=== xdotool ==="
xdotool version
echo "=== scrot ==="
scrot --version 2>&1 || echo "scrot OK"
- name: Run Linux replay smoke test
run: |
pnpm clean:daemon
node --experimental-strip-types src/bin.ts test test/integration/replays/linux \
--retries 3 \
--report-junit test/artifacts/replays-linux.junit.xml
- name: Upload Linux artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: linux-artifacts
if-no-files-found: ignore
path: |
test/artifacts/**
test/screenshots/**