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)
|
name: Deploy stage (admin)
|
||||||
|
|
||||||
# Auto после успешного CI на master: тот же sha-*, плюс floating :stage.
|
# Auto после успешного E2E IFT на master: promote sha-* → :stage и SSH deploy.
|
||||||
# Product version (VERSION) не бампается — только sha в рантайме/registry.
|
# Product version (VERSION) не бампается — только sha в рантайме/registry.
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: [CI]
|
workflows: ["E2E IFT (admin)"]
|
||||||
types: [completed]
|
types: [completed]
|
||||||
branches: [master, main]
|
branches: [master, main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
name: E2E IFT smoke (admin)
|
name: E2E IFT (admin)
|
||||||
|
|
||||||
# После успешного Deploy IFT — smoke против живого IFT (без API-моков).
|
# После успешного Deploy IFT — полный UI-сьют (mock-тесты на живом SPA + smoke API).
|
||||||
# Учётки берутся с хоста IFT из /opt/eventhub-ift/.env
|
# Учётки smoke — из /opt/eventhub-ift/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
||||||
# (SMOKE_ADMIN_* → fallback ADMIN_SUPER_*; см. EventHubDevOps/ift/.env.example).
|
|
||||||
# Опц. secret E2E_IFT_BASE_URL (default https://admin-ui.ift.eventhub.local).
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["Deploy IFT (admin)"]
|
workflows: ["Deploy IFT (admin)"]
|
||||||
@@ -15,12 +13,12 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ift-smoke:
|
ift-e2e:
|
||||||
if: |
|
if: |
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -40,33 +38,9 @@ jobs:
|
|||||||
SSH_HOST: ${{ secrets.IFT_SSH_HOST }}
|
SSH_HOST: ${{ secrets.IFT_SSH_HOST }}
|
||||||
SSH_USER: ${{ secrets.IFT_SSH_USER }}
|
SSH_USER: ${{ secrets.IFT_SSH_USER }}
|
||||||
SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
||||||
run: |
|
run: bash scripts/load-stand-smoke-env.sh ift
|
||||||
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}"
|
|
||||||
|
|
||||||
- name: IFT smoke
|
- name: E2E IFT (full UI suite)
|
||||||
env:
|
env:
|
||||||
E2E_TARGET: ift
|
E2E_TARGET: ift
|
||||||
E2E_IFT_BASE_URL: ${{ secrets.E2E_IFT_BASE_URL }}
|
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` | 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 учётки
|
### 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`.
|
Локально: `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 .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:e2e": "playwright test --project=mock",
|
"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": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
|||||||
+24
-7
@@ -2,6 +2,13 @@ import { defineConfig, devices } from '@playwright/test';
|
|||||||
|
|
||||||
const port = Number(process.env.E2E_PORT || 4173);
|
const port = Number(process.env.E2E_PORT || 4173);
|
||||||
const baseURL = process.env.E2E_BASE_URL || `http://127.0.0.1:${port}`;
|
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({
|
export default defineConfig({
|
||||||
testDir: './e2e/tests',
|
testDir: './e2e/tests',
|
||||||
@@ -24,17 +31,27 @@ export default defineConfig({
|
|||||||
testIgnore: /ift\//,
|
testIgnore: /ift\//,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ift-smoke',
|
name: 'ift-ui',
|
||||||
use: {
|
use: { ...liveChrome, baseURL: iftBaseURL },
|
||||||
...devices['Desktop Chrome'],
|
testIgnore: /ift\//,
|
||||||
baseURL: process.env.E2E_IFT_BASE_URL || 'https://admin-ui.ift.eventhub.local',
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'ift-smoke',
|
||||||
|
use: { ...liveChrome, baseURL: iftBaseURL },
|
||||||
|
testMatch: /ift\//,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stage-ui',
|
||||||
|
use: { ...liveChrome, baseURL: stageBaseURL },
|
||||||
|
testIgnore: /ift\//,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stage-smoke',
|
||||||
|
use: { ...liveChrome, baseURL: stageBaseURL },
|
||||||
testMatch: /ift\//,
|
testMatch: /ift\//,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
webServer:
|
webServer: liveTarget
|
||||||
process.env.E2E_TARGET === 'ift'
|
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
command: `npm run preview -- --host 127.0.0.1 --port ${port}`,
|
command: `npm run preview -- --host 127.0.0.1 --port ${port}`,
|
||||||
|
|||||||
@@ -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