Files
SnapOtter/.github/workflows/nightly.yml
T
SnapOtter e44be61053 test: add real device-emulated mobile and tablet testing (phase 3)
Replace the fake resized-desktop mobile specs with real Playwright device
projects (Pixel 7, iPhone 14, iPad gen 7, Galaxy Tab S9) that exercise
real touch, mobile UA, DPR, and WebKit engine.

Device projects in playwright.config.ts:
- mobile-chromium (Pixel 7, 412x839, Chromium)
- mobile-webkit (iPhone 14, 390x664, WebKit)
- tablet-webkit (iPad gen 7, 810x1080, WebKit)
- tablet-chromium (Galaxy Tab S9, 640x1024, Chromium)

Device specs (16 mobile, 10 tablet):
- Core flow: navigate to tool, upload, process, download
- Responsive chrome: bottom-nav, sidebar hidden, search, overflow
- Touch interactions: before-after slider, crop canvas
- Editor gate: phone asserts "Desktop Recommended" message
- Editor tablet: iPad (810px) renders canvas, Galaxy Tab (640px) shows gate
- SSE visibility-recovery regression guard
- RTL Arabic locale responsive check

Key finding: Galaxy Tab S9 viewport (640px) is below the 768px mobile
breakpoint, so useMobile() returns true and the editor shows the mobile
gate. Only iPad gen 7 (810px) is classified as non-mobile.

Component tests (17 tests, Vitest/jsdom):
- use-mobile hook: breakpoint behavior across all 4 device widths
- mobile-bottom-nav: render, navigation links, settings callback, icons

CI wiring:
- ci.yml: mobile-chromium smoke job (PR gate)
- nightly.yml: full device matrix with webkit
- update-visual-baselines.yml: webkit + device projects for goldens

Parity: 13218 passed, 0 dropped (PARITY OK)
2026-06-20 02:15:50 +08:00

356 lines
13 KiB
YAML

name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * *"
permissions:
contents: read
env:
SYSTEM_DEPS: libheif-examples libheif-plugin-x265 libheif-plugin-libde265 libimage-exiftool-perl imagemagick ghostscript libjxl-tools libopenjp2-tools ffmpeg qpdf
jobs:
e2e-full:
name: E2E Full (${{ matrix.shard }}/4)
runs-on: ubuntu-latest
timeout-minutes: 60
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U snapotter"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- uses: ./.github/actions/setup
- name: Get Playwright version
id: pw-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Install Playwright Chromium
run: pnpm playwright install --with-deps chromium
- name: Run full e2e suite (shard)
run: pnpm playwright test --project=chromium --shard=${{ matrix.shard }}/4
env:
PW_WORKERS: "2"
- name: Upload report on failure
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: e2e-full-report-shard-${{ matrix.shard }}
path: playwright-report/
retention-days: 7
e2e-serial:
name: E2E Serial Bucket
runs-on: ubuntu-latest
timeout-minutes: 60
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U snapotter"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- uses: ./.github/actions/setup
- name: Get Playwright version
id: pw-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Install Playwright Chromium
run: pnpm playwright install --with-deps chromium
- name: Run serial bucket (global-state specs)
run: pnpm playwright test --project=chromium-serial --workers=1
- name: Upload report on failure
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: e2e-serial-report
path: playwright-report/
retention-days: 7
e2e-cross-browser:
name: E2E Cross-Browser (Firefox + WebKit)
runs-on: ubuntu-latest
timeout-minutes: 30
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U snapotter"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: ./.github/actions/setup
- name: Get Playwright version
id: pw-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: playwright-all-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Install Playwright browsers
run: pnpm playwright install --with-deps chromium firefox webkit
- name: Run cross-browser spec
run: pnpm playwright test --project=firefox --project=webkit
env:
PW_WORKERS: "1"
- name: Upload report on failure
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: e2e-cross-browser-report
path: playwright-report/
retention-days: 7
e2e-device-matrix:
name: E2E Device Matrix (mobile + tablet)
runs-on: ubuntu-latest
timeout-minutes: 30
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U snapotter"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- uses: ./.github/actions/setup
- name: Get Playwright version
id: pw-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: playwright-all-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Install Playwright browsers (chromium + webkit)
run: pnpm playwright install --with-deps chromium webkit
- name: Run all device projects
run: pnpm playwright test --project=mobile-chromium --project=mobile-webkit --project=tablet-webkit --project=tablet-chromium
env:
PW_WORKERS: "2"
- name: Upload report on failure
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: e2e-device-matrix-report
path: playwright-report/
retention-days: 7
docker-e2e:
name: Docker Container E2E
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Run container test suite
run: docker compose -f docker/docker-compose.test.yml up --build --exit-code-from test-e2e
extended-matrix:
name: Extended Matrix + Fuzz
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- name: Allow ImageMagick to read EPS/PS via Ghostscript delegate
run: |
POLICY_FILE=$(find /etc/ImageMagick* -name policy.xml 2>/dev/null | head -1)
if [ -n "$POLICY_FILE" ]; then
sudo sed -i 's/<policy domain="coder" rights="none" pattern="EPS"/<policy domain="coder" rights="read" pattern="EPS"/' "$POLICY_FILE"
sudo sed -i 's/<policy domain="coder" rights="none" pattern="PS"/<policy domain="coder" rights="read" pattern="PS"/' "$POLICY_FILE"
fi
- uses: ./.github/actions/setup
- name: Run integration suite with full matrix and fuzz enabled
run: pnpm vitest run tests/integration/ --reporter=verbose
env:
FULL_MATRIX: "1"
FUZZ: "1"
FUZZ_RUNS: "50"
api-fuzz:
name: Schemathesis API Fuzz
runs-on: ubuntu-latest
timeout-minutes: 45
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U snapotter"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- uses: ./.github/actions/setup
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- name: Install Schemathesis
run: pip install schemathesis
- name: Start API server
run: |
mkdir -p /tmp/st-data
AUTH_ENABLED=false ANALYTICS_ENABLED=false \
DATABASE_URL=postgres://snapotter:snapotter@localhost:5432/snapotter \
REDIS_URL=redis://localhost:6379 \
WORKSPACE_PATH=/tmp/st-data/workspace DATA_DIR=/tmp/st-data \
pnpm --filter @snapotter/api dev &
for i in $(seq 1 60); do
if curl -fsS http://localhost:13490/api/v1/health > /dev/null 2>&1; then
echo "API up after ${i}s"; exit 0
fi
sleep 1
done
echo "API failed to start"; exit 1
- name: Fuzz tool endpoints from the OpenAPI spec
run: |
schemathesis run http://localhost:13490/api/v1/openapi.yaml \
--url http://localhost:13490 \
--checks not_a_server_error \
--include-path-regex "^/api/v1/(tools|health|info)" \
--max-examples 25 \
--report junit \
--report-dir st-report
- name: Upload fuzz report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: schemathesis-report
path: st-report/
retention-days: 14
coverage-report:
name: Coverage Report
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install system dependencies
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends $SYSTEM_DEPS
- name: Allow ImageMagick to read EPS/PS via Ghostscript delegate
run: |
POLICY_FILE=$(find /etc/ImageMagick* -name policy.xml 2>/dev/null | head -1)
if [ -n "$POLICY_FILE" ]; then
sudo sed -i 's/<policy domain="coder" rights="none" pattern="EPS"/<policy domain="coder" rights="read" pattern="EPS"/' "$POLICY_FILE"
sudo sed -i 's/<policy domain="coder" rights="none" pattern="PS"/<policy domain="coder" rights="read" pattern="PS"/' "$POLICY_FILE"
fi
- uses: ./.github/actions/setup
- name: Run tests with coverage
run: pnpm vitest run --coverage tests/unit/ tests/integration/
- name: Upload coverage artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
path: coverage/
retention-days: 14