From aa30d06e922805ded9ed17cde374bad534c5ef6e Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Sat, 11 Jul 2026 04:56:49 +0300 Subject: [PATCH 1/3] ci: build/test/check/doctor on macOS + Linux + automation smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds .github/workflows/ci.yml with two jobs: - verify (macOS + Linux): native doctor, validate, headless null-platform tests (native test -Dplatform=null), and strict markup/manifest check; macOS also builds the release binary. - smoke (Linux, Xvfb): builds the app with automation enabled, drives it headless through the embedded automation server, asserts the widget tree rendered non-blank, and uploads the deterministic screenshot. scripts/smoke.sh is the portable driver — Xvfb in CI, direct launch on a local desktop — so the same smoke test runs everywhere. Closes #2 --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++++++++ scripts/smoke.sh | 50 +++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100755 scripts/smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11a3157 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +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 + - 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..836fa1c --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,50 @@ +#!/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" + +if command -v xvfb-run >/dev/null 2>&1; then + xvfb-run -a "$BIN" & +else + "$BIN" & +fi +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, non-blank widget tree. +native automate assert --timeout-ms 30000 \ + 'ready=true' \ + 'gpu_nonblank=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" From 605cec9f812bc8426d4db9ea33c31d71ef3b74db Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Sat, 11 Jul 2026 05:04:38 +0300 Subject: [PATCH 2/3] ci: make Linux smoke job headless-robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run the app under a private session bus (dbus-run-session) with the accessibility bus disabled (GTK_A11Y=none) so it no longer aborts on the missing a11y DBus service under Xvfb. Drop the GPU-dependent gpu_nonblank assertion — the semantics-tree and screenshot checks are GPU-independent and sufficient. Add dbus to the job's apt packages. --- .github/workflows/ci.yml | 2 +- scripts/smoke.sh | 28 +++++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11a3157..5599aae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: 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 + run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev xvfb dbus - name: Install Native SDK CLI run: npm install -g @native-sdk/cli@0.4.1 - name: Build headless app (automation enabled) diff --git a/scripts/smoke.sh b/scripts/smoke.sh index 836fa1c..31922c1 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -19,21 +19,35 @@ fi # Start from a clean automation dir so 'wait' only sees this run. rm -rf "$AUTO_DIR" -if command -v xvfb-run >/dev/null 2>&1; then - xvfb-run -a "$BIN" & -else - "$BIN" & -fi +# 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, non-blank widget tree. +# 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' \ - 'gpu_nonblank=true' \ 'role=button name="Reset"' \ 'role=button name="\+"' \ 'role=button name="-"' \ From e5379a82569ead61b380483d3e73b7de4e530171 Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Sat, 11 Jul 2026 05:12:43 +0300 Subject: [PATCH 3/3] ci: allow unprivileged user namespaces on Linux smoke runner Ubuntu 24.04 GitHub runners set kernel.apparmor_restrict_unprivileged_userns=1, which blocks the user namespace bubblewrap needs to sandbox the SDK's dbus-proxy (bwrap: setting up uid map: Permission denied). Relax the sysctl before launching the app. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5599aae..ef81918 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,10 @@ jobs: 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)