feat(ci): последовательный deploy IFT -> E2E -> stage -> E2E.
Stage деплоится только после полного UI-сьюта на IFT; после stage — E2E на stage (ift-ui/stage-ui + smoke). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
name: Deploy stage (admin)
|
||||
|
||||
# Auto после успешного CI на master: тот же sha-*, плюс floating :stage.
|
||||
# Auto после успешного E2E IFT на master: promote sha-* → :stage и SSH deploy.
|
||||
# Product version (VERSION) не бампается — только sha в рантайме/registry.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
workflows: ["E2E IFT (admin)"]
|
||||
types: [completed]
|
||||
branches: [master, main]
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
name: E2E IFT smoke (admin)
|
||||
name: E2E IFT (admin)
|
||||
|
||||
# После успешного Deploy IFT — smoke против живого IFT (без API-моков).
|
||||
# Учётки берутся с хоста IFT из /opt/eventhub-ift/.env
|
||||
# (SMOKE_ADMIN_* → fallback ADMIN_SUPER_*; см. EventHubDevOps/ift/.env.example).
|
||||
# Опц. secret E2E_IFT_BASE_URL (default https://admin-ui.ift.eventhub.local).
|
||||
# После успешного Deploy IFT — полный UI-сьют (mock-тесты на живом SPA + smoke API).
|
||||
# Учётки smoke — из /opt/eventhub-ift/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Deploy IFT (admin)"]
|
||||
@@ -15,12 +13,12 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
ift-smoke:
|
||||
ift-e2e:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -40,33 +38,9 @@ jobs:
|
||||
SSH_HOST: ${{ secrets.IFT_SSH_HOST }}
|
||||
SSH_USER: ${{ secrets.IFT_SSH_USER }}
|
||||
SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KEY=$(mktemp)
|
||||
printf '%s\n' "$SSH_KEY" > "$KEY"
|
||||
chmod 600 "$KEY"
|
||||
# Источник истины — env на стенде (не отдельные Gitea E2E secrets).
|
||||
REMOTE=$(ssh -i "$KEY" -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
|
||||
"${SSH_USER}@${SSH_HOST}" \
|
||||
"grep -E '^(SMOKE_ADMIN_EMAIL|SMOKE_ADMIN_PASSWORD|ADMIN_SUPER_EMAIL|ADMIN_SUPER_PASSWORD)=' /opt/eventhub-ift/.env || true")
|
||||
rm -f "$KEY"
|
||||
EMAIL=$(printf '%s\n' "$REMOTE" | sed -n 's/^SMOKE_ADMIN_EMAIL=//p' | head -1)
|
||||
PASS=$(printf '%s\n' "$REMOTE" | sed -n 's/^SMOKE_ADMIN_PASSWORD=//p' | head -1)
|
||||
if [ -z "$EMAIL" ]; then
|
||||
EMAIL=$(printf '%s\n' "$REMOTE" | sed -n 's/^ADMIN_SUPER_EMAIL=//p' | head -1)
|
||||
fi
|
||||
if [ -z "$PASS" ]; then
|
||||
PASS=$(printf '%s\n' "$REMOTE" | sed -n 's/^ADMIN_SUPER_PASSWORD=//p' | head -1)
|
||||
fi
|
||||
if [ -z "$EMAIL" ] || [ -z "$PASS" ]; then
|
||||
echo "::error::В /opt/eventhub-ift/.env нет SMOKE_ADMIN_* / ADMIN_SUPER_*"
|
||||
exit 1
|
||||
fi
|
||||
echo "SMOKE_ADMIN_EMAIL=${EMAIL}" >> "$GITHUB_ENV"
|
||||
echo "SMOKE_ADMIN_PASSWORD=${PASS}" >> "$GITHUB_ENV"
|
||||
echo "Loaded smoke admin email: ${EMAIL}"
|
||||
run: bash scripts/load-stand-smoke-env.sh ift
|
||||
|
||||
- name: IFT smoke
|
||||
- name: E2E IFT (full UI suite)
|
||||
env:
|
||||
E2E_TARGET: ift
|
||||
E2E_IFT_BASE_URL: ${{ secrets.E2E_IFT_BASE_URL }}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: E2E stage (admin)
|
||||
|
||||
# После успешного Deploy stage — полный UI-сьют (mock-тесты на живом SPA + smoke API).
|
||||
# Учётки smoke — из /opt/eventhub-stage/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Deploy stage (admin)"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: eventhub-frontadmin-e2e-stage
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
stage-e2e:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- name: Install Playwright browser
|
||||
run: bash scripts/install-playwright-chromium.sh
|
||||
|
||||
- name: Load smoke credentials from stage .env
|
||||
env:
|
||||
SSH_HOST: ${{ secrets.STAGE_SSH_HOST }}
|
||||
SSH_USER: ${{ secrets.STAGE_SSH_USER }}
|
||||
SSH_KEY: ${{ secrets.STAGE_SSH_PRIVATE_KEY }}
|
||||
run: bash scripts/load-stand-smoke-env.sh stage
|
||||
|
||||
- name: E2E stage (full UI suite)
|
||||
env:
|
||||
E2E_TARGET: stage
|
||||
E2E_STAGE_BASE_URL: ${{ secrets.E2E_STAGE_BASE_URL }}
|
||||
run: npm run test:e2e:stage
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: playwright-stage-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
+3
-2
@@ -29,7 +29,8 @@ Helper: `src/lib/testid.ts`.
|
||||
| Команда | Назначение |
|
||||
|---------|------------|
|
||||
| `npm run test:e2e` | mock-suite (CI / PR), API через Playwright route |
|
||||
| `npm run test:e2e:ift` | IFT smoke (живой API), см. ниже |
|
||||
| `npm run test:e2e:ift` | IFT: полный UI-сьют на живом SPA + smoke API |
|
||||
| `npm run test:e2e:stage` | stage: полный UI-сьют на живом SPA + smoke API |
|
||||
|
||||
### IFT / stage учётки
|
||||
|
||||
@@ -42,4 +43,4 @@ Helper: `src/lib/testid.ts`.
|
||||
|
||||
Локально: `set -a && source /path/to/ift/.env && set +a && npm run test:e2e:ift`.
|
||||
|
||||
Workflow `e2e-ift.yml` после Deploy IFT читает те же ключи по SSH с `/opt/eventhub-ift/.env` (нужны уже имеющиеся `IFT_SSH_*`). Опц. secret `E2E_IFT_BASE_URL`.
|
||||
Workflow `e2e-ift.yml` / `e2e-stage.yml` читают те же ключи по SSH с `/opt/eventhub-{ift,stage}/.env`. Опц. secrets `E2E_IFT_BASE_URL`, `E2E_STAGE_BASE_URL`.
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"test:e2e": "playwright test --project=mock",
|
||||
"test:e2e:ift": "E2E_TARGET=ift playwright test --project=ift-smoke"
|
||||
"test:e2e:ift": "E2E_TARGET=ift playwright test --project=ift-ui --project=ift-smoke",
|
||||
"test:e2e:stage": "E2E_TARGET=stage playwright test --project=stage-ui --project=stage-smoke"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
|
||||
+31
-14
@@ -2,6 +2,13 @@ import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const port = Number(process.env.E2E_PORT || 4173);
|
||||
const baseURL = process.env.E2E_BASE_URL || `http://127.0.0.1:${port}`;
|
||||
const iftBaseURL = process.env.E2E_IFT_BASE_URL || 'https://admin-ui.ift.eventhub.local';
|
||||
const stageBaseURL = process.env.E2E_STAGE_BASE_URL || 'https://admin-ui.stage.eventhub.local';
|
||||
const liveTarget = process.env.E2E_TARGET;
|
||||
const liveChrome = {
|
||||
...devices['Desktop Chrome'],
|
||||
ignoreHTTPSErrors: true,
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e/tests',
|
||||
@@ -23,23 +30,33 @@ export default defineConfig({
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
testIgnore: /ift\//,
|
||||
},
|
||||
{
|
||||
name: 'ift-ui',
|
||||
use: { ...liveChrome, baseURL: iftBaseURL },
|
||||
testIgnore: /ift\//,
|
||||
},
|
||||
{
|
||||
name: 'ift-smoke',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
baseURL: process.env.E2E_IFT_BASE_URL || 'https://admin-ui.ift.eventhub.local',
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
use: { ...liveChrome, baseURL: iftBaseURL },
|
||||
testMatch: /ift\//,
|
||||
},
|
||||
{
|
||||
name: 'stage-ui',
|
||||
use: { ...liveChrome, baseURL: stageBaseURL },
|
||||
testIgnore: /ift\//,
|
||||
},
|
||||
{
|
||||
name: 'stage-smoke',
|
||||
use: { ...liveChrome, baseURL: stageBaseURL },
|
||||
testMatch: /ift\//,
|
||||
},
|
||||
],
|
||||
webServer:
|
||||
process.env.E2E_TARGET === 'ift'
|
||||
? undefined
|
||||
: {
|
||||
command: `npm run preview -- --host 127.0.0.1 --port ${port}`,
|
||||
url: baseURL,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
webServer: liveTarget
|
||||
? undefined
|
||||
: {
|
||||
command: `npm run preview -- --host 127.0.0.1 --port ${port}`,
|
||||
url: baseURL,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load SMOKE_ADMIN_* (fallback ADMIN_SUPER_*) from stand .env via SSH into GITHUB_ENV.
|
||||
# Usage: load-stand-smoke-env.sh <ift|stage>
|
||||
set -euo pipefail
|
||||
|
||||
STAND="${1:?stand: ift|stage}"
|
||||
case "${STAND}" in
|
||||
ift)
|
||||
SSH_HOST="${SSH_HOST:?IFT_SSH_HOST required}"
|
||||
SSH_USER="${SSH_USER:?IFT_SSH_USER required}"
|
||||
SSH_KEY="${SSH_KEY:?IFT_SSH_PRIVATE_KEY required}"
|
||||
ENV_FILE="/opt/eventhub-ift/.env"
|
||||
;;
|
||||
stage)
|
||||
SSH_HOST="${SSH_HOST:?STAGE_SSH_HOST required}"
|
||||
SSH_USER="${SSH_USER:?STAGE_SSH_USER required}"
|
||||
SSH_KEY="${SSH_KEY:?STAGE_SSH_PRIVATE_KEY required}"
|
||||
ENV_FILE="/opt/eventhub-stage/.env"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown stand: ${STAND}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
KEY="$(mktemp)"
|
||||
printf '%s\n' "${SSH_KEY}" > "${KEY}"
|
||||
chmod 600 "${KEY}"
|
||||
|
||||
REMOTE="$(ssh -i "${KEY}" -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
|
||||
"${SSH_USER}@${SSH_HOST}" \
|
||||
"grep -E '^(SMOKE_ADMIN_EMAIL|SMOKE_ADMIN_PASSWORD|ADMIN_SUPER_EMAIL|ADMIN_SUPER_PASSWORD)=' ${ENV_FILE} || true")"
|
||||
rm -f "${KEY}"
|
||||
|
||||
EMAIL="$(printf '%s\n' "${REMOTE}" | sed -n 's/^SMOKE_ADMIN_EMAIL=//p' | head -1)"
|
||||
PASS="$(printf '%s\n' "${REMOTE}" | sed -n 's/^SMOKE_ADMIN_PASSWORD=//p' | head -1)"
|
||||
if [[ -z "${EMAIL}" ]]; then
|
||||
EMAIL="$(printf '%s\n' "${REMOTE}" | sed -n 's/^ADMIN_SUPER_EMAIL=//p' | head -1)"
|
||||
fi
|
||||
if [[ -z "${PASS}" ]]; then
|
||||
PASS="$(printf '%s\n' "${REMOTE}" | sed -n 's/^ADMIN_SUPER_PASSWORD=//p' | head -1)"
|
||||
fi
|
||||
if [[ -z "${EMAIL}" || -z "${PASS}" ]]; then
|
||||
echo "::error::В ${ENV_FILE} нет SMOKE_ADMIN_* / ADMIN_SUPER_*"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
echo "SMOKE_ADMIN_EMAIL=${EMAIL}" >> "${GITHUB_ENV}"
|
||||
echo "SMOKE_ADMIN_PASSWORD=${PASS}" >> "${GITHUB_ENV}"
|
||||
fi
|
||||
echo "Loaded smoke admin email: ${EMAIL}"
|
||||
Reference in New Issue
Block a user