phase 2: ui testing infrastructure

Vitest + @testing-library/jest-dom matchers wired through tests/setup.ts.
Playwright with four projects: chromium-desktop, webkit-desktop,
chromium-mobile-iphone-13, chromium-mobile-pixel-5; traces and
screenshots retained on failure.

.gitea/workflows/ui-test.yaml runs Tier 1 on every push and pull
request: monorepo Go service tests (backend with -p 1 to dodge
testcontainer contention; gateway, game, every pkg/<name> module),
pnpm install --frozen-lockfile, playwright install --with-deps,
pnpm test, pnpm exec playwright test. Uploads playwright-report
and test-results on failure. Integration suite stays gated behind
make -C integration integration; deprecated client/ excluded.

.gitea/workflows/ui-release.yaml mirrors Tier 1 on v* tag push and
keeps commented placeholders for visual regression (Phase 33) and
macOS iOS smoke (Phase 32).

ui/docs/testing.md documents both tiers and the local invocations
that mirror what CI runs. ui/PLAN.md Phase 2 marked done; Phase 3
gains a bullet to extend the go test command with ./ui/core/...;
Phase 36 has the renamed release workflow path.

tools/local-ci/ ships a self-contained docker-compose for verifying
workflows against a local Gitea + arm64 act_runner before pushing
to a real instance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 08:24:44 +02:00
parent cf41be9eff
commit 7450006ed3
17 changed files with 885 additions and 15 deletions
+145
View File
@@ -0,0 +1,145 @@
name: ui-release
# Tier 2 (release) workflow. Runs on tag push.
#
# Currently mirrors the Tier 1 step set. Visual regression baseline
# checks and the macOS-runner iOS smoke job are landed in later phases
# of ui/PLAN.md and live as commented sections at the end of this file
# until those phases ship.
on:
push:
tags:
- 'v*'
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.work
cache: true
- name: Run Go tests
# client/ is the deprecated Fyne client; excluded from CI per
# ui/PLAN.md §74. -count=1 disables Go's test cache so a green
# run never depends on a previous runner's cached state. The
# backend suite is run with -p 1 because most backend packages
# spawn their own Postgres testcontainer, and parallel
# Postgres bootstraps starve each other on a constrained
# runner. pkg modules are listed one by one because ./pkg/...
# does not recurse across the independent go.work modules
# under pkg/.
run: |
go test -count=1 -p 1 ./backend/...
go test -count=1 \
./gateway/... \
./game/... \
./pkg/calc/... \
./pkg/connector/... \
./pkg/cronutil/... \
./pkg/error/... \
./pkg/geoip/... \
./pkg/model/... \
./pkg/postgres/... \
./pkg/redisconn/... \
./pkg/schema/... \
./pkg/storage/... \
./pkg/transcoder/... \
./pkg/util/...
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 11.0.7
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: ui/pnpm-lock.yaml
- name: Install npm dependencies
working-directory: ui
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
working-directory: ui/frontend
run: pnpm exec playwright install --with-deps
- name: Run Vitest
working-directory: ui/frontend
run: pnpm test
- name: Run Playwright
working-directory: ui/frontend
run: pnpm exec playwright test
- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: ui/frontend/playwright-report/
retention-days: 14
- name: Upload Playwright traces on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces
path: ui/frontend/test-results/
retention-days: 14
# visual-regression: enabled in Phase 33 of ui/PLAN.md, once the PWA
# shell and service worker land and a snapshot baseline is committed
# under ui/frontend/tests/__snapshots__/.
#
# visual-regression:
# runs-on: ubuntu-latest
# needs: test
# steps:
# - uses: actions/checkout@v4
# - uses: pnpm/action-setup@v4
# with: { version: 11.0.7 }
# - uses: actions/setup-node@v4
# with:
# node-version: 22
# cache: pnpm
# cache-dependency-path: ui/pnpm-lock.yaml
# - working-directory: ui
# run: pnpm install --frozen-lockfile
# - working-directory: ui/frontend
# run: pnpm exec playwright install --with-deps
# - working-directory: ui/frontend
# run: pnpm exec playwright test --grep @visual
# ios-smoke: enabled in Phase 32 of ui/PLAN.md, once the Capacitor
# wrapper lands. Runs a Capacitor + Appium smoke against an iOS
# simulator on a macOS runner.
#
# ios-smoke:
# runs-on: macos-13
# needs: test
# steps:
# - uses: actions/checkout@v4
# - uses: pnpm/action-setup@v4
# with: { version: 11.0.7 }
# - uses: actions/setup-node@v4
# with:
# node-version: 22
# cache: pnpm
# cache-dependency-path: ui/pnpm-lock.yaml
# - working-directory: ui
# run: pnpm install --frozen-lockfile
# - working-directory: ui/mobile
# run: pnpm exec cap sync ios && pnpm exec appium-smoke ios
+119
View File
@@ -0,0 +1,119 @@
name: ui-test
# Tier 1 (per-PR) workflow. Runs Vitest + Playwright for the UI client and
# the monorepo Go service tests (everything except the integration suite,
# which lives behind `make -C integration integration` and needs a Docker
# daemon set up for testcontainers).
#
# The path filter is intentionally broad until a dedicated go-test
# workflow is introduced; this is the only CI gate today.
on:
push:
paths:
- 'ui/**'
- 'backend/**'
- 'gateway/**'
- 'game/**'
- 'pkg/**'
- 'go.work'
- 'go.work.sum'
- '.gitea/workflows/ui-test.yaml'
pull_request:
paths:
- 'ui/**'
- 'backend/**'
- 'gateway/**'
- 'game/**'
- 'pkg/**'
- 'go.work'
- 'go.work.sum'
- '.gitea/workflows/ui-test.yaml'
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.work
cache: true
- name: Run Go tests
# client/ is the deprecated Fyne client; excluded from CI per
# ui/PLAN.md §74. -count=1 disables Go's test cache so a green
# run never depends on a previous runner's cached state. The
# backend suite is run with -p 1 because most backend packages
# spawn their own Postgres testcontainer, and parallel
# Postgres bootstraps starve each other on a constrained
# runner. pkg modules are listed one by one because ./pkg/...
# does not recurse across the independent go.work modules
# under pkg/.
run: |
go test -count=1 -p 1 ./backend/...
go test -count=1 \
./gateway/... \
./game/... \
./pkg/calc/... \
./pkg/connector/... \
./pkg/cronutil/... \
./pkg/error/... \
./pkg/geoip/... \
./pkg/model/... \
./pkg/postgres/... \
./pkg/redisconn/... \
./pkg/schema/... \
./pkg/storage/... \
./pkg/transcoder/... \
./pkg/util/...
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 11.0.7
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: ui/pnpm-lock.yaml
- name: Install npm dependencies
working-directory: ui
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
working-directory: ui/frontend
run: pnpm exec playwright install --with-deps
- name: Run Vitest
working-directory: ui/frontend
run: pnpm test
- name: Run Playwright
working-directory: ui/frontend
run: pnpm exec playwright test
- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: ui/frontend/playwright-report/
retention-days: 14
- name: Upload Playwright traces on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces
path: ui/frontend/test-results/
retention-days: 14
+1
View File
@@ -0,0 +1 @@
.env
+42
View File
@@ -0,0 +1,42 @@
.PHONY: help up down logs status clean push
.DEFAULT_GOAL := help
COMPOSE := docker compose
GITEA_USER := galaxy
GITEA_PASS := galaxy-dev
REPO_NAME := galaxy
REMOTE_NAME := local-gitea
REPO_ROOT := $(realpath $(CURDIR)/../..)
GIT := git -C $(REPO_ROOT)
REMOTE_URL := http://$(GITEA_USER):$(GITEA_PASS)@localhost:3000/$(GITEA_USER)/$(REPO_NAME).git
help:
@echo "Local Gitea CI for galaxy:"
@echo " make up Bring up Gitea + runner (idempotent)"
@echo " make down Stop both containers"
@echo " make logs Tail logs"
@echo " make status Show container status"
@echo " make push Push current branch to local Gitea"
@echo " make clean Stop and wipe all local state"
up:
@./bootstrap.sh
down:
$(COMPOSE) down
logs:
$(COMPOSE) logs -f --tail=50
status:
$(COMPOSE) ps
push:
@$(GIT) remote get-url $(REMOTE_NAME) >/dev/null 2>&1 || \
$(GIT) remote add $(REMOTE_NAME) $(REMOTE_URL)
$(GIT) push $(REMOTE_NAME) HEAD
clean:
$(COMPOSE) down -v
rm -f .env
+98
View File
@@ -0,0 +1,98 @@
# Local Gitea CI
Self-contained Gitea + Actions runner for verifying
`.gitea/workflows/*` honestly before pushing to a real Gitea instance.
Runs natively on arm64 (Apple Silicon) — every image below has an
arm64 variant, so Docker pulls the right architecture and the runner
executes workflow steps without QEMU emulation.
## Prerequisites
- Docker (Colima or Docker Desktop)
- `python3`, `curl`, `bash` — all built into macOS
## First time
```sh
make -C tools/local-ci up
```
This:
1. brings up the Gitea container;
2. creates an admin user (`galaxy` / `galaxy-dev`);
3. creates the `galaxy/galaxy` repo;
4. fetches a runner registration token from the Gitea API;
5. brings up the runner with that token (the runner persists its
credentials in a Docker volume and ignores the token on subsequent
restarts).
The script is idempotent — re-running it is safe.
## Pushing a branch
```sh
make -C tools/local-ci push
```
This adds a `local-gitea` remote on the first run and then pushes the
current `HEAD`. Equivalent manual flow:
```sh
git remote add local-gitea \
http://galaxy:galaxy-dev@localhost:3000/galaxy/galaxy.git
git push local-gitea HEAD
```
The Tier 1 workflow fires on `push` to any branch and the Tier 2
workflow fires on tags matching `v*`. Watch runs at:
<http://localhost:3000/galaxy/galaxy/actions>
## Operational targets
| Target | What it does |
| ---------------- | -------------------------------------------- |
| `make up` | Bring up Gitea + runner (idempotent) |
| `make down` | Stop both containers (state preserved) |
| `make logs` | Tail logs from both containers |
| `make status` | Show container status |
| `make push` | Push current `HEAD` to local Gitea |
| `make clean` | Stop and wipe all local state (full reset) |
## What's in the box
| Component | Image | Role |
| ---------- | ---------------------------------- | ------------------------------------------- |
| Gitea | `gitea/gitea:1.23` | Server with SQLite backend |
| act_runner | `gitea/act_runner:0.6.1` | Single-capacity runner registered on boot |
| Workflow | `catthehacker/ubuntu:act-latest` | Image spawned per job (multi-arch) |
The runner mounts the host Docker socket and spawns workflow
containers on the same Docker network as Gitea, so
`actions/checkout` reaches the server at `http://gitea:3000` from
inside spawned containers.
## Caveats
- Gitea's `ROOT_URL` is set to `http://gitea:3000/` so spawned
workflow containers reach the server through the compose network.
The web UI works at `http://localhost:3000` via port mapping, but
copy-paste URLs in the UI may show `gitea:3000` instead of
`localhost:3000`. Harmless for local dev; switch the host part by
hand when copying.
- The runner is single-capacity (`runner.capacity: 1` in
`config.yaml`). Concurrent jobs queue. Bump if you need parallel
jobs.
- First push from a fresh checkout uploads the full repo history
(~tens of MB). Subsequent pushes are deltas.
- `actions/upload-artifact@v4` requires Gitea ≥ 1.21 — we pin
`1.23` to stay above the cutoff.
- Workflow steps run as `root` inside the spawned container; this
matches the upstream catthehacker behaviour. Keep that in mind if
you add steps that touch host-mounted directories.
- On Apple Silicon the runner image and its catthehacker child run
natively as arm64. Some pre-built tools that ship in the image are
amd64-only and would fall back to QEMU; `setup-go`, `setup-node`,
and `pnpm/action-setup` all download arm64 binaries themselves, so
the workflow steps we care about stay native.
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Bring up Gitea, create the admin user and the galaxy/galaxy repo,
# fetch a runner registration token, bring up the runner.
# Idempotent — re-runnable.
set -euo pipefail
cd "$(dirname "$0")"
GITEA_USER=galaxy
GITEA_PASS=galaxy-dev
GITEA_EMAIL=galaxy@local
REPO_NAME=galaxy
GITEA_URL=http://localhost:3000
echo ">>> Bringing up Gitea..."
docker compose up -d gitea
echo ">>> Waiting for Gitea API..."
for _ in $(seq 1 120); do
if curl -fsS "${GITEA_URL}/api/v1/version" >/dev/null 2>&1; then
echo "Gitea is up."
break
fi
sleep 1
done
if ! curl -fsS "${GITEA_URL}/api/v1/version" >/dev/null 2>&1; then
echo "Gitea did not come up within 120 seconds." >&2
docker compose logs gitea | tail -30 >&2
exit 1
fi
echo ">>> Creating admin user (idempotent)..."
docker compose exec -T gitea su git -c "
gitea admin user create \
--username ${GITEA_USER} \
--password ${GITEA_PASS} \
--email ${GITEA_EMAIL} \
--admin \
--must-change-password=false 2>&1 || true
"
echo ">>> Creating repo (idempotent)..."
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
-u "${GITEA_USER}:${GITEA_PASS}" \
-H "Content-Type: application/json" \
-d "{\"name\":\"${REPO_NAME}\",\"private\":true,\"auto_init\":false}" \
"${GITEA_URL}/api/v1/user/repos")
case "${HTTP_CODE}" in
201) echo "Repo created." ;;
409) echo "Repo already exists." ;;
*)
echo "Unexpected response (${HTTP_CODE}) creating repo." >&2
exit 1
;;
esac
echo ">>> Fetching runner registration token..."
RUNNER_TOKEN=$(curl -fsS \
-u "${GITEA_USER}:${GITEA_PASS}" \
"${GITEA_URL}/api/v1/admin/runners/registration-token" \
| python3 -c "import json, sys; print(json.load(sys.stdin)['token'])")
# act_runner uses RUNNER_TOKEN only on the first boot. After registration
# it persists credentials in the named runner-data volume (/data/.runner)
# and ignores the env token on subsequent restarts. Writing a fresh token
# every time is harmless.
echo "RUNNER_TOKEN=${RUNNER_TOKEN}" > .env
echo ">>> Bringing up runner..."
docker compose up -d runner
cat <<EOF
Done.
Push the current branch and watch a run:
cd $(cd ../.. && pwd)
git remote add local-gitea http://${GITEA_USER}:${GITEA_PASS}@localhost:3000/${GITEA_USER}/${REPO_NAME}.git 2>/dev/null || true
git push local-gitea HEAD
open http://localhost:3000/${GITEA_USER}/${REPO_NAME}/actions
Or use \`make push\` from this directory.
EOF
+35
View File
@@ -0,0 +1,35 @@
# act_runner configuration.
#
# The `ubuntu-latest` label is mapped to catthehacker/ubuntu:act-latest,
# which is multi-arch — Docker on Apple Silicon pulls the arm64 variant
# and runs it natively (no QEMU). The same image is what `act` uses
# locally, so workflows behave the same.
log:
level: info
runner:
file: /data/.runner
capacity: 1
fetch_timeout: 5s
fetch_interval: 2s
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
cache:
enabled: true
dir: /data/cache
container:
# Spawned workflow containers join the same network as Gitea so
# actions/checkout and other steps can reach the server at
# http://gitea:3000.
network: galaxy-local-gitea-net
privileged: false
options: ""
workdir_parent: ""
valid_volumes: []
force_pull: false
host:
workdir_parent: ""
+78
View File
@@ -0,0 +1,78 @@
# Local Gitea + Actions runner for verifying .gitea/workflows/*.
# Runs natively on arm64 (Apple Silicon) — every image below is multi-arch.
#
# Browser: http://localhost:3000
# API: http://localhost:3000/api/v1
# Push URL: http://galaxy:galaxy-dev@localhost:3000/galaxy/galaxy.git
# Actions: http://localhost:3000/galaxy/galaxy/actions
#
# `bootstrap.sh` (or `make up`) brings everything up and registers the
# runner. State persists in named Docker volumes; `make clean` wipes them.
services:
gitea:
image: gitea/gitea:1.23
container_name: galaxy-local-gitea
restart: unless-stopped
environment:
USER_UID: "1000"
USER_GID: "1000"
GITEA__database__DB_TYPE: sqlite3
GITEA__database__PATH: /data/gitea/gitea.db
# ROOT_URL uses the in-network hostname so the runner and spawned
# workflow containers reach Gitea through the compose network.
# The browser still works at http://localhost:3000 via the port
# mapping below; UI-generated copy URLs may show "gitea:3000",
# which is harmless for local dev.
GITEA__server__ROOT_URL: http://gitea:3000/
GITEA__server__SSH_PORT: "2222"
GITEA__actions__ENABLED: "true"
GITEA__security__INSTALL_LOCK: "true"
GITEA__service__DISABLE_REGISTRATION: "true"
ports:
- "3000:3000"
- "2222:22"
volumes:
- gitea-data:/data
networks:
- gitea-net
healthcheck:
test:
- CMD-SHELL
- wget -q -O- http://localhost:3000/api/v1/version >/dev/null || exit 1
interval: 5s
timeout: 3s
retries: 30
start_period: 5s
runner:
image: gitea/act_runner:0.6.1
container_name: galaxy-local-runner
restart: unless-stopped
depends_on:
gitea:
condition: service_healthy
environment:
CONFIG_FILE: /config/config.yaml
GITEA_INSTANCE_URL: http://gitea:3000
# Provided by bootstrap.sh in the .env file. After the first
# successful registration, act_runner persists credentials in
# /data/.runner and ignores this token on subsequent restarts.
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN:-}
GITEA_RUNNER_NAME: galaxy-local
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- runner-data:/data
- ./config.yaml:/config/config.yaml:ro
networks:
- gitea-net
networks:
gitea-net:
name: galaxy-local-gitea-net
volumes:
gitea-data:
name: galaxy-local-gitea-data
runner-data:
name: galaxy-local-runner-data
+39 -14
View File
@@ -287,9 +287,9 @@ Targeted tests:
- a single Vitest smoke test that mounts the landing component and - a single Vitest smoke test that mounts the landing component and
asserts the rendered version string is non-empty. asserts the rendered version string is non-empty.
## Phase 2. Testing Infrastructure ## ~~Phase 2. Testing Infrastructure~~
Status: pending. Status: done.
Goal: install and configure the test toolchain that every later phase Goal: install and configure the test toolchain that every later phase
depends on, including Tier 1 (per-PR) and Tier 2 (release) targets. depends on, including Tier 1 (per-PR) and Tier 2 (release) targets.
@@ -299,17 +299,37 @@ Artifacts:
- `ui/frontend/package.json` dev-dependencies (added on top of the - `ui/frontend/package.json` dev-dependencies (added on top of the
Phase 1 minimum of `vitest`, `jsdom`, `@testing-library/svelte`): Phase 1 minimum of `vitest`, `jsdom`, `@testing-library/svelte`):
`@testing-library/jest-dom`, `playwright`, `@playwright/test` `@testing-library/jest-dom`, `playwright`, `@playwright/test`
- `ui/frontend/vitest.config.ts` extended for `@testing-library/jest-dom` - `ui/frontend/vitest.config.ts` extended with `setupFiles:
matchers (the JSDOM environment itself is wired in Phase 1) ["./tests/setup.ts"]` to wire `@testing-library/jest-dom` matchers
- `ui/frontend/playwright.config.ts` with three projects: into Vitest (the JSDOM environment itself is wired in Phase 1)
- `ui/frontend/tests/setup.ts` registering `jest-dom` matchers
- `ui/frontend/tests/e2e/landing.spec.ts` placeholder Playwright test
asserting the version footer renders
- `ui/frontend/playwright.config.ts` with four projects:
`chromium-desktop`, `webkit-desktop`, `chromium-mobile-iphone-13`, `chromium-desktop`, `webkit-desktop`, `chromium-mobile-iphone-13`,
`chromium-mobile-pixel-5`; tracing and screenshots enabled on failure `chromium-mobile-pixel-5`; tracing and screenshots enabled on
- `ui/.gitea/workflows/test.yaml` running Tier 1 on every push and PR failure; `webServer: pnpm run dev` on port 5173
on a Linux runner: `go test ./...`, `pnpm test`, `pnpm exec - `.gitea/workflows/ui-test.yaml` running Tier 1 on every push and PR
playwright install --with-deps`, `pnpm exec playwright test` on a Linux runner: monorepo Go service tests for `backend/`,
- `ui/.gitea/workflows/release.yaml` running Tier 2 on tag push: `gateway/`, `game/`, and every `pkg/<name>/` module (each pkg
visual regression baseline check, optional macOS runner block for module is enumerated explicitly because they sit as independent
iOS smoke (Phase 32+ only) go.work modules under a shared `pkg/` directory, and `./pkg/...`
does not recurse across module boundaries). All Go tests run with
`-count=1` so the cache never masks a failing run; backend tests
additionally run with `-p 1` because most backend packages spawn
their own Postgres testcontainer and parallel bootstraps starve
each other on the runner. The integration suite stays gated behind
`make -C integration integration` and lives outside Tier 1; the
deprecated `client/` Fyne client (see §74) is also excluded — its
tests, code, and documentation are frozen and CI must not run
them. Then `pnpm install --frozen-lockfile` from `ui/`,
`pnpm exec playwright install --with-deps`, `pnpm test`,
`pnpm exec playwright test`; Playwright reports and traces
uploaded as artefacts on failure
- `.gitea/workflows/ui-release.yaml` running Tier 2 on tag push (`v*`):
same Tier 1 step set today; visual-regression and macOS-runner
iOS-smoke jobs live as commented sections marked with the phase
number that re-enables them (Phase 33 and Phase 32 respectively)
- `ui/docs/testing.md` topic doc naming the two tiers, the tools - `ui/docs/testing.md` topic doc naming the two tiers, the tools
per tier, and the rule that visual regression baselines live in per tier, and the rule that visual regression baselines live in
`ui/frontend/tests/__snapshots__/` until shifted to Argos `ui/frontend/tests/__snapshots__/` until shifted to Argos
@@ -322,7 +342,9 @@ Acceptance criteria:
- a placeholder Playwright test passes in `chromium-desktop` and - a placeholder Playwright test passes in `chromium-desktop` and
`webkit-desktop` projects locally; `webkit-desktop` projects locally;
- the Gitea Actions Tier 1 workflow runs end-to-end against a clean - the Gitea Actions Tier 1 workflow runs end-to-end against a clean
clone of the repo on a Linux runner. clone of the repo on a Linux runner. Until the Gitea runner is
provisioned, the workflow is exercised locally with
`act -W .gitea/workflows/ui-test.yaml`.
Targeted tests: Targeted tests:
@@ -342,6 +364,9 @@ implementation. No network, no UI.
Artifacts: Artifacts:
- `ui/core/go.mod` module declared in the project Go workspace - `ui/core/go.mod` module declared in the project Go workspace
- `.gitea/workflows/ui-test.yaml` and `.gitea/workflows/ui-release.yaml`
extended to add `./ui/core/...` to the Tier 1 / Tier 2 `go test`
command list introduced in Phase 2
- `ui/core/canon/` canonical bytes for `galaxy-request-v1`, - `ui/core/canon/` canonical bytes for `galaxy-request-v1`,
`galaxy-response-v1`, and `galaxy-event-v1`, matching `galaxy-response-v1`, and `galaxy-event-v1`, matching
`docs/ARCHITECTURE.md` §15 byte-for-byte `docs/ARCHITECTURE.md` §15 byte-for-byte
@@ -1702,7 +1727,7 @@ Artifacts:
follow-up issue to switch to self-hosted Argos follow-up issue to switch to self-hosted Argos
- Appium harness for iOS Simulator and Android Emulator covering the - Appium harness for iOS Simulator and Android Emulator covering the
login flow, push-event flow, and at least one full turn loop; login flow, push-event flow, and at least one full turn loop;
`.gitea/workflows/release.yaml` extended with macOS-runner Appium `.gitea/workflows/ui-release.yaml` extended with macOS-runner Appium
job (mandatory pre-release gate) job (mandatory pre-release gate)
Dependencies: Phases 1 through 35. Dependencies: Phases 1 through 35.
+92
View File
@@ -0,0 +1,92 @@
# UI Testing Tiers
UI client test toolchain. Project-wide testing layers (service /
inter-service / system) live in [`../../docs/TESTING.md`](../../docs/TESTING.md);
this doc only covers the UI-specific tiers added in Phase 2 of
[`../PLAN.md`](../PLAN.md).
## Tier 1 — per-PR
Triggered by `.gitea/workflows/ui-test.yaml` on every push and pull
request that touches `ui/**`, `backend/**`, `gateway/**`, `game/**`,
`pkg/**`, `client/**`, `go.work`, or `go.work.sum`. Linux runner only.
Runs:
- `go test` over the monorepo Go modules, excluding two areas:
- `integration/` — needs Docker + testcontainers and is the
project's `make -C integration integration` gate.
- `client/` — the deprecated Fyne client (see `../PLAN.md` §74) is
frozen; its tests are not run in CI.
The `pkg/<name>/` modules are listed one by one in the workflow
because they are independent go.work modules and `./pkg/...` does
not recurse into separate modules. The exact command lives in
`.gitea/workflows/ui-test.yaml`.
- `pnpm test` (Vitest + `@testing-library/svelte` +
`@testing-library/jest-dom`) — component / unit tests under
`ui/frontend/tests/**/*.test.ts`.
- `pnpm exec playwright test` — end-to-end smoke against `pnpm run
dev` on port 5173. Four projects:
- `chromium-desktop` (Desktop Chrome)
- `webkit-desktop` (Desktop Safari)
- `chromium-mobile-iphone-13` (iPhone 13 viewport, Chromium engine)
- `chromium-mobile-pixel-5` (Pixel 5 viewport, Chromium engine)
Playwright traces and screenshots are retained on failure and uploaded
as Gitea Actions artefacts (`playwright-report` and `playwright-traces`,
14-day retention).
## Tier 2 — release
Triggered by `.gitea/workflows/ui-release.yaml` on tag push (`v*`).
Currently mirrors the Tier 1 step set; the dedicated release-only
checks land in later phases:
- **Visual regression baseline check** — Phase 33. Snapshots live in
`ui/frontend/tests/__snapshots__/` until the project shifts to
Argos or another visual-diff service.
- **iOS smoke (Capacitor + Appium)** — Phase 32. Runs on a `macos-13`
runner once the Capacitor mobile wrapper exists.
Both blocks are present as commented sections in
`.gitea/workflows/ui-release.yaml` with the phase number that
re-enables them.
## Local execution
From `ui/frontend/`:
```sh
pnpm test # Vitest
pnpm exec playwright install # one-time
pnpm exec playwright test # all projects
pnpm exec playwright test --project=chromium-desktop
pnpm exec playwright show-report # open last HTML report
```
From the repository root, the same scope CI uses (backend serially
because most packages spawn their own Postgres testcontainer and
parallel bootstraps starve each other on constrained runners):
```sh
go test -count=1 -p 1 ./backend/...
go test -count=1 \
./gateway/... ./game/... \
./pkg/calc/... ./pkg/connector/... ./pkg/cronutil/... \
./pkg/error/... ./pkg/geoip/... ./pkg/model/... \
./pkg/postgres/... ./pkg/redisconn/... ./pkg/schema/... \
./pkg/storage/... ./pkg/transcoder/... ./pkg/util/...
```
## CI dry-run with `act`
Until the Gitea Actions runner is wired up, the workflow is exercised
locally with [`act`](https://github.com/nektos/act):
```sh
act -W .gitea/workflows/ui-test.yaml --container-architecture linux/amd64
```
`act` reads the workflow as GitHub Actions; the format is
intentionally compatible.
+5 -1
View File
@@ -8,15 +8,19 @@
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"test": "vitest run" "test": "vitest run",
"test:e2e": "playwright test"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.59.1",
"@sveltejs/adapter-static": "^3.0.0", "@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.59.0", "@sveltejs/kit": "^2.59.0",
"@sveltejs/vite-plugin-svelte": "^7.0.0", "@sveltejs/vite-plugin-svelte": "^7.0.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.2.0", "@testing-library/svelte": "^5.2.0",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"jsdom": "^25.0.0", "jsdom": "^25.0.0",
"playwright": "^1.59.1",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tslib": "^2.6.0", "tslib": "^2.6.0",
+33
View File
@@ -0,0 +1,33 @@
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "tests/e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: [["list"], ["html", { open: "never" }]],
use: {
baseURL: "http://localhost:5173",
trace: "retain-on-failure",
screenshot: "only-on-failure",
},
projects: [
{ name: "chromium-desktop", use: { ...devices["Desktop Chrome"] } },
{ name: "webkit-desktop", use: { ...devices["Desktop Safari"] } },
// devices["iPhone 13"] picks WebKit by default; the project name
// here claims a Chromium engine on a mobile viewport, so the
// browser is explicitly overridden. WebKit on a desktop viewport
// is already covered by webkit-desktop.
{
name: "chromium-mobile-iphone-13",
use: { ...devices["iPhone 13"], browserName: "chromium" },
},
{ name: "chromium-mobile-pixel-5", use: { ...devices["Pixel 5"] } },
],
webServer: {
command: "pnpm run dev",
url: "http://localhost:5173",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
+8
View File
@@ -0,0 +1,8 @@
import { expect, test } from "@playwright/test";
test("landing page renders the version string", async ({ page }) => {
await page.goto("/");
const footer = page.getByTestId("app-version");
await expect(footer).toBeVisible();
await expect(footer).toContainText(/version\s+\S+/);
});
+1
View File
@@ -6,6 +6,7 @@ describe("landing page", () => {
it("renders a non-empty version string in the footer", () => { it("renders a non-empty version string in the footer", () => {
const { getByTestId } = render(Page); const { getByTestId } = render(Page);
const footer = getByTestId("app-version"); const footer = getByTestId("app-version");
expect(footer).toBeInTheDocument();
expect(footer.textContent?.trim()).not.toBe(""); expect(footer.textContent?.trim()).not.toBe("");
expect(footer.textContent).toMatch(/version\s+\S+/); expect(footer.textContent).toMatch(/version\s+\S+/);
}); });
+1
View File
@@ -0,0 +1 @@
import "@testing-library/jest-dom/vitest";
+1
View File
@@ -12,6 +12,7 @@ export default mergeConfig(
environment: "jsdom", environment: "jsdom",
include: ["tests/**/*.test.ts"], include: ["tests/**/*.test.ts"],
globals: true, globals: true,
setupFiles: ["./tests/setup.ts"],
}, },
}), }),
); );
+101
View File
@@ -8,6 +8,9 @@ importers:
frontend: frontend:
devDependencies: devDependencies:
'@playwright/test':
specifier: ^1.59.1
version: 1.59.1
'@sveltejs/adapter-static': '@sveltejs/adapter-static':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.10(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@7.1.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17)))(svelte@5.55.5)(typescript@5.9.3)(vite@8.0.10(@types/node@22.19.17))) version: 3.0.10(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@7.1.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17)))(svelte@5.55.5)(typescript@5.9.3)(vite@8.0.10(@types/node@22.19.17)))
@@ -17,6 +20,9 @@ importers:
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.1.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17)) version: 7.1.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17))
'@testing-library/jest-dom':
specifier: ^6.9.1
version: 6.9.1
'@testing-library/svelte': '@testing-library/svelte':
specifier: ^5.2.0 specifier: ^5.2.0
version: 5.3.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17))(vitest@4.1.5(@types/node@22.19.17)(jsdom@25.0.1)(vite@8.0.10(@types/node@22.19.17))) version: 5.3.1(svelte@5.55.5)(vite@8.0.10(@types/node@22.19.17))(vitest@4.1.5(@types/node@22.19.17)(jsdom@25.0.1)(vite@8.0.10(@types/node@22.19.17)))
@@ -26,6 +32,9 @@ importers:
jsdom: jsdom:
specifier: ^25.0.0 specifier: ^25.0.0
version: 25.0.1 version: 25.0.1
playwright:
specifier: ^1.59.1
version: 1.59.1
svelte: svelte:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.55.5 version: 5.55.5
@@ -47,6 +56,9 @@ importers:
packages: packages:
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
'@asamuzakjp/css-color@3.2.0': '@asamuzakjp/css-color@3.2.0':
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
@@ -124,6 +136,11 @@ packages:
'@oxc-project/types@0.127.0': '@oxc-project/types@0.127.0':
resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==}
'@playwright/test@1.59.1':
resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==}
engines: {node: '>=18'}
hasBin: true
'@polka/url@1.0.0-next.29': '@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
@@ -265,6 +282,10 @@ packages:
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@testing-library/jest-dom@6.9.1':
resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
'@testing-library/svelte-core@1.0.0': '@testing-library/svelte-core@1.0.0':
resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==} resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -399,6 +420,9 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
cssstyle@4.6.0: cssstyle@4.6.0:
resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -441,6 +465,9 @@ packages:
dom-accessibility-api@0.5.16: dom-accessibility-api@0.5.16:
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
dunder-proto@1.0.1: dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -499,6 +526,11 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -547,6 +579,10 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
indent-string@4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
is-potential-custom-element-name@1.0.1: is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@@ -668,6 +704,10 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
mri@1.2.0: mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -703,6 +743,16 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'} engines: {node: '>=12'}
playwright-core@1.59.1:
resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==}
engines: {node: '>=18'}
hasBin: true
playwright@1.59.1:
resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==}
engines: {node: '>=18'}
hasBin: true
postcss@8.5.14: postcss@8.5.14:
resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -722,6 +772,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'} engines: {node: '>= 14.18.0'}
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
rolldown@1.0.0-rc.17: rolldown@1.0.0-rc.17:
resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
@@ -764,6 +818,10 @@ packages:
std-env@4.1.0: std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
strip-indent@3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
svelte-check@4.4.8: svelte-check@4.4.8:
resolution: {integrity: sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==} resolution: {integrity: sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==}
engines: {node: '>= 18.0.0'} engines: {node: '>= 18.0.0'}
@@ -966,6 +1024,8 @@ packages:
snapshots: snapshots:
'@adobe/css-tools@4.4.4': {}
'@asamuzakjp/css-color@3.2.0': '@asamuzakjp/css-color@3.2.0':
dependencies: dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
@@ -1048,6 +1108,10 @@ snapshots:
'@oxc-project/types@0.127.0': {} '@oxc-project/types@0.127.0': {}
'@playwright/test@1.59.1':
dependencies:
playwright: 1.59.1
'@polka/url@1.0.0-next.29': {} '@polka/url@1.0.0-next.29': {}
'@rolldown/binding-android-arm64@1.0.0-rc.17': '@rolldown/binding-android-arm64@1.0.0-rc.17':
@@ -1151,6 +1215,15 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
pretty-format: 27.5.1 pretty-format: 27.5.1
'@testing-library/jest-dom@6.9.1':
dependencies:
'@adobe/css-tools': 4.4.4
aria-query: 5.3.1
css.escape: 1.5.1
dom-accessibility-api: 0.6.3
picocolors: 1.1.1
redent: 3.0.0
'@testing-library/svelte-core@1.0.0(svelte@5.55.5)': '@testing-library/svelte-core@1.0.0(svelte@5.55.5)':
dependencies: dependencies:
svelte: 5.55.5 svelte: 5.55.5
@@ -1270,6 +1343,8 @@ snapshots:
cookie@0.6.0: {} cookie@0.6.0: {}
css.escape@1.5.1: {}
cssstyle@4.6.0: cssstyle@4.6.0:
dependencies: dependencies:
'@asamuzakjp/css-color': 3.2.0 '@asamuzakjp/css-color': 3.2.0
@@ -1298,6 +1373,8 @@ snapshots:
dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.5.16: {}
dom-accessibility-api@0.6.3: {}
dunder-proto@1.0.1: dunder-proto@1.0.1:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
@@ -1347,6 +1424,9 @@ snapshots:
hasown: 2.0.3 hasown: 2.0.3
mime-types: 2.1.35 mime-types: 2.1.35
fsevents@2.3.2:
optional: true
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@@ -1404,6 +1484,8 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
indent-string@4.0.0: {}
is-potential-custom-element-name@1.0.1: {} is-potential-custom-element-name@1.0.1: {}
is-reference@3.0.3: is-reference@3.0.3:
@@ -1509,6 +1591,8 @@ snapshots:
dependencies: dependencies:
mime-db: 1.52.0 mime-db: 1.52.0
min-indent@1.0.1: {}
mri@1.2.0: {} mri@1.2.0: {}
mrmime@2.0.1: {} mrmime@2.0.1: {}
@@ -1531,6 +1615,14 @@ snapshots:
picomatch@4.0.4: {} picomatch@4.0.4: {}
playwright-core@1.59.1: {}
playwright@1.59.1:
dependencies:
playwright-core: 1.59.1
optionalDependencies:
fsevents: 2.3.2
postcss@8.5.14: postcss@8.5.14:
dependencies: dependencies:
nanoid: 3.3.12 nanoid: 3.3.12
@@ -1549,6 +1641,11 @@ snapshots:
readdirp@4.1.2: {} readdirp@4.1.2: {}
redent@3.0.0:
dependencies:
indent-string: 4.0.0
strip-indent: 3.0.0
rolldown@1.0.0-rc.17: rolldown@1.0.0-rc.17:
dependencies: dependencies:
'@oxc-project/types': 0.127.0 '@oxc-project/types': 0.127.0
@@ -1600,6 +1697,10 @@ snapshots:
std-env@4.1.0: {} std-env@4.1.0: {}
strip-indent@3.0.0:
dependencies:
min-indent: 1.0.1
svelte-check@4.4.8(picomatch@4.0.4)(svelte@5.55.5)(typescript@5.9.3): svelte-check@4.4.8(picomatch@4.0.4)(svelte@5.55.5)(typescript@5.9.3):
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31