diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ef81918 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: verify (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 22 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - name: Install Native SDK CLI + run: npm install -g @native-sdk/cli@0.4.1 + - name: Doctor (strict, macOS) + if: runner.os == 'macOS' + run: native doctor --strict + - name: Doctor (Linux) + if: runner.os == 'Linux' + run: native doctor + - name: Validate manifest + run: native validate app.zon + - name: Logic + view tests (headless null platform) + run: native test -Dplatform=null + - name: Check markup + manifest + run: native check --strict + - name: Build release binary (macOS) + if: runner.os == 'macOS' + run: native build + + smoke: + name: automation smoke (Linux ยท Xvfb) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 22 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - name: Install GTK + Xvfb + run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev xvfb dbus + - name: Allow unprivileged user namespaces (bwrap dbus-proxy sandbox) + # Ubuntu 24.04 runners restrict unprivileged userns via AppArmor, + # which breaks the SDK's bubblewrap-sandboxed dbus-proxy. + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + - name: Install Native SDK CLI + run: npm install -g @native-sdk/cli@0.4.1 + - name: Build headless app (automation enabled) + run: native build -Dplatform=linux -Dautomation=true + - name: Drive the app headless and assert the widget tree + run: ./scripts/smoke.sh + - name: Upload rendered screenshot + if: always() + uses: actions/upload-artifact@v4 + with: + name: smoke-screenshot + path: .zig-cache/native-sdk-automation/screenshot-main-canvas.png + if-no-files-found: ignore diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100755 index 0000000..31922c1 --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Automation smoke test: launch the app, wait for its embedded automation +# server, assert the widget tree actually rendered, and capture a +# deterministic screenshot artifact. Portable โ€” uses a virtual framebuffer +# (Xvfb) when available (CI), otherwise runs the window directly (local +# macOS). The app must be built with -Dautomation=true. +# +set -euo pipefail + +BIN=zig-out/bin/studio +AUTO_DIR=.zig-cache/native-sdk-automation + +if [ ! -x "$BIN" ]; then + echo "smoke: $BIN not found โ€” build first: native build -Dautomation=true" >&2 + exit 1 +fi + +# Start from a clean automation dir so 'wait' only sees this run. +rm -rf "$AUTO_DIR" + +# Headless GTK needs no accessibility bus; without this the app aborts on a +# missing a11y DBus service. A private session bus (dbus-run-session) then +# satisfies the runtime's DBus handshake. Both are no-ops on macOS. +export GTK_A11Y=none +export NO_AT_BRIDGE=1 + +launch() { + if command -v xvfb-run >/dev/null 2>&1; then + if command -v dbus-run-session >/dev/null 2>&1; then + dbus-run-session -- xvfb-run -a "$BIN" + else + xvfb-run -a "$BIN" + fi + else + "$BIN" + fi +} + +launch & +APP_PID=$! +trap 'kill "$APP_PID" >/dev/null 2>&1 || true' EXIT + +# Block until the runtime publishes ready=true (or fail loudly on timeout). +native automate wait --timeout-ms 60000 + +# The window must exist with the expected widget tree. These come from the +# semantics snapshot (GPU-independent), so they hold under software GL too. +native automate assert --timeout-ms 30000 \ + 'ready=true' \ + 'role=button name="Reset"' \ + 'role=button name="\+"' \ + 'role=button name="-"' \ + 'OCPP DebugKit Studio' \ + 'count: 0' + +# No runtime/dispatch errors surfaced during startup. +native automate assert --absent 'error event=' + +# A real-pixel capture must land as a non-empty PNG. +native automate screenshot main-canvas +test -s "$AUTO_DIR/screenshot-main-canvas.png" + +echo "smoke: ok โ€” window rendered and automation drove the widget tree"