food-market/.forgejo/workflows/ci.yml
nns 1989db32bb test(s16): regression suite 35 flows + visual 60 snapshots + nightly + CI badges
Sprint 16 — постоянный regression-контур: flows + visual + nightly +
CI workflow + README badges.

Ключевые цифры:
- 35 flow-тестов: 35/35 ✓ за ~30 секунд (workers=2 локально).
- 60 visual snapshot'ов (15 страниц × 2 темы × 2 viewport'a):
  60/60 ✓ за ~4 минуты с retries=1.
- Полный регресс прогон: ~5 минут (цель была < 15).

Что сделано:
1. tests/regression/ — Playwright + factories + 8 spec-файлов.
   OrgFactory builder создаёт org через API за O(N) HTTP вызовов
   (signup → token → refs → products → counterparties → posted supplies).
   Каждый flow независим, использует свой fresh-org.
2. tests/regression/visual/ — 15 страниц × 2 темы × 2 viewport'a.
   Маски на динамический контент (артикулы с Date.now, KPI'ы,
   delta-стрелки) чтобы 0.2% threshold не флакал. snapshotPathTemplate
   c {projectName} — desktop+mobile не затирают друг друга.
3. tests/regression/factories/OrgFactory.ts — builder с .withProducts
   .withCounterparties .withSupplies. Retry signup'a на 429.
4. .forgejo/workflows/regression.yml — on workflow_run после
   Docker API/Web; cache на pnpm-store + Playwright-browsers;
   артефакты при failure; Telegram-уведомление в обоих случаях.
5. ~/nightly-verify.sh + cron `0 4 * * *`: health → redeploy если
   нужно → smoke flows; в воскресенье полный flows+visual. Логи с
   ротацией 14 дней. Telegram на провал (~/.fm-watchdog/telegram-*).
6. scripts/generate-badges.sh — coverage из cobertura.xml в SVG через
   shields.io (offline fallback). 4 CI-status badge + coverage badge в
   README; CI step «Update coverage badge» авто-коммитит обновлённый
   SVG на push в main.

Локальное число flake'ов: 1/60 visual на retry=1 (product-new light) —
случайная гонка маски, retry'ит и проходит.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:11 +05:00

128 lines
4.3 KiB
YAML

name: CI
on:
push:
branches: [main]
tags: ['v*']
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'
pull_request:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
backend:
name: Backend (.NET 8)
runs-on: [self-hosted, linux]
services:
postgres:
image: 127.0.0.1:5001/mirror/postgres:16-alpine
env:
POSTGRES_DB: food_market_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5441:5432
steps:
- uses: actions/checkout@v4
# dotnet 8 SDK is pre-installed on the self-hosted runner host.
- name: Dotnet version
run: dotnet --version
# Кэшируем NuGet-пакеты по hash *.csproj — restore становится мгновенным,
# если зависимости не менялись.
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', 'food-market.sln') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore food-market.sln
- name: Build
run: dotnet build food-market.sln --no-restore -c Release
- name: Test
env:
ConnectionStrings__Default: Host=localhost;Port=5441;Database=food_market_test;Username=postgres;Password=postgres
run: dotnet test food-market.sln --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory TestResults || echo "No tests yet"
# Sprint 16: пересчитываем coverage-badge и коммитим обновлённый
# SVG обратно в репо. Шаг no-op если ничего не изменилось.
- name: Update coverage badge
if: success() && github.ref == 'refs/heads/main'
run: |
bash scripts/generate-badges.sh
if ! git diff --quiet badges/coverage.svg 2>/dev/null; then
git config user.email "ci@food-market.local"
git config user.name "Forgejo CI"
git add badges/coverage.svg
git commit -m "chore(badges): update coverage [skip ci]" || true
git push || echo "push skipped (no token / detached HEAD)"
fi
web:
name: Web (React + Vite)
runs-on: [self-hosted, linux]
defaults:
run:
working-directory: src/food-market.web
steps:
- uses: actions/checkout@v4
# node 20 + pnpm are pre-installed on the self-hosted runner host.
- name: Node + pnpm version
run: node --version && pnpm --version
# Кэшируем pnpm store по hash pnpm-lock.yaml — install становится мгновенным
# при отсутствии изменений в зависимостях.
- name: Resolve pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ runner.os }}-${{ hashFiles('src/food-market.web/pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: Install
run: pnpm install --frozen-lockfile
- name: Build (tsc + vite)
run: pnpm build
# POS build requires Windows — no Forgejo runner for it; skipped silently.
pos:
name: POS (WPF, Windows)
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Build POS
run: |
dotnet restore src/food-market.pos/food-market.pos.csproj
dotnet build src/food-market.pos/food-market.pos.csproj --no-restore -c Release
dotnet publish src/food-market.pos/food-market.pos.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish