fix(e2e): интеграционные тесты на стендах без mocks.
ift-ui/stage-ui гоняют stand/integration против живого API; mock-suite остаётся в CI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
name: E2E IFT (admin)
|
name: E2E IFT (admin)
|
||||||
|
|
||||||
# После успешного Deploy IFT — полный UI-сьют (mock-тесты на живом SPA + smoke API).
|
# После успешного Deploy IFT — интеграционный UI-сьют против живого admin-api (без mocks).
|
||||||
# Учётки smoke — из /opt/eventhub-ift/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
# Учётки smoke — из /opt/eventhub-ift/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
||||||
run: bash scripts/load-stand-smoke-env.sh ift
|
run: bash scripts/load-stand-smoke-env.sh ift
|
||||||
|
|
||||||
- name: E2E IFT (full UI suite)
|
- name: E2E IFT (integration, live API)
|
||||||
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 }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: E2E stage (admin)
|
name: E2E stage (admin)
|
||||||
|
|
||||||
# После успешного Deploy stage — полный UI-сьют (mock-тесты на живом SPA + smoke API).
|
# После успешного Deploy stage — интеграционный UI-сьют против живого admin-api (без mocks).
|
||||||
# Учётки smoke — из /opt/eventhub-stage/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
# Учётки smoke — из /opt/eventhub-stage/.env по SSH (SMOKE_ADMIN_* / ADMIN_SUPER_*).
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
SSH_KEY: ${{ secrets.STAGE_SSH_PRIVATE_KEY }}
|
SSH_KEY: ${{ secrets.STAGE_SSH_PRIVATE_KEY }}
|
||||||
run: bash scripts/load-stand-smoke-env.sh stage
|
run: bash scripts/load-stand-smoke-env.sh stage
|
||||||
|
|
||||||
- name: E2E stage (full UI suite)
|
- name: E2E stage (integration, live API)
|
||||||
env:
|
env:
|
||||||
E2E_TARGET: stage
|
E2E_TARGET: stage
|
||||||
E2E_STAGE_BASE_URL: ${{ secrets.E2E_STAGE_BASE_URL }}
|
E2E_STAGE_BASE_URL: ${{ secrets.E2E_STAGE_BASE_URL }}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
/** Учётки superadmin со стенда (CI: load-stand-smoke-env.sh). */
|
||||||
|
export function standAdminEmail(): string | undefined {
|
||||||
|
return process.env.SMOKE_ADMIN_EMAIL || process.env.ADMIN_SUPER_EMAIL || process.env.E2E_ADMIN_EMAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function standAdminPassword(): string | undefined {
|
||||||
|
return (
|
||||||
|
process.env.SMOKE_ADMIN_PASSWORD ||
|
||||||
|
process.env.ADMIN_SUPER_PASSWORD ||
|
||||||
|
process.env.E2E_ADMIN_PASSWORD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasStandCredentials(): boolean {
|
||||||
|
return Boolean(standAdminEmail() && standAdminPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Реальный login против admin-api стенда (без Playwright route mocks). */
|
||||||
|
export async function loginAsStandAdmin(page: Page) {
|
||||||
|
const email = standAdminEmail();
|
||||||
|
const password = standAdminPassword();
|
||||||
|
if (!email || !password) {
|
||||||
|
throw new Error('Stand admin credentials missing (SMOKE_ADMIN_* / ADMIN_SUPER_*)');
|
||||||
|
}
|
||||||
|
await page.goto('/login');
|
||||||
|
await page.getByLabel(/email/i).fill(email);
|
||||||
|
await page.getByLabel(/пароль|password/i).fill(password);
|
||||||
|
await page.getByTestId('btn-login').click();
|
||||||
|
await page.getByTestId('layout-shell').waitFor({ state: 'visible', timeout: 30_000 });
|
||||||
|
}
|
||||||
@@ -134,12 +134,14 @@ export function createContentSeed() {
|
|||||||
{
|
{
|
||||||
id: 'audit-e2e-1',
|
id: 'audit-e2e-1',
|
||||||
admin_id: 'admin-e2e-1',
|
admin_id: 'admin-e2e-1',
|
||||||
action: 'user.update',
|
email: 'e2e@eventhub.local',
|
||||||
target_type: 'user',
|
role: 'superadmin',
|
||||||
target_id: 'user-e2e-1',
|
action: 'update_user',
|
||||||
details: { status: 'frozen' },
|
entity_type: 'user',
|
||||||
|
entity_id: 'user-e2e-1',
|
||||||
|
timestamp: now,
|
||||||
ip: '127.0.0.1',
|
ip: '127.0.0.1',
|
||||||
created_at: now,
|
reason: 'e2e',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { hasStandCredentials, loginAsStandAdmin } from '../../helpers/standAuth';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Интеграция UI ↔ admin-api на IFT/stage после деплоя.
|
||||||
|
* Без page.route mocks — только живой SPA и API стенда.
|
||||||
|
*/
|
||||||
|
test.describe('stand integration (live API)', () => {
|
||||||
|
test.skip(!hasStandCredentials(), 'SMOKE_ADMIN_* / ADMIN_SUPER_* from stand .env required');
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await loginAsStandAdmin(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dashboard после login', async ({ page }) => {
|
||||||
|
await expect(page.getByTestId('page-dashboard')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('audit: страница и таблица без ошибки рендера', async ({ page }) => {
|
||||||
|
await page.goto('/audit');
|
||||||
|
await expect(page.getByTestId('page-audit')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('filter-audit')).toBeVisible();
|
||||||
|
await expect(page.getByText(/Ошибка отображения|Minified React error/i)).toHaveCount(0);
|
||||||
|
await expect(page.locator('[data-testid="page-audit"] table tbody tr').first()).toBeVisible({
|
||||||
|
timeout: 20_000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users: список загружается', async ({ page }) => {
|
||||||
|
await page.goto('/users');
|
||||||
|
await expect(page.getByTestId('page-users')).toBeVisible();
|
||||||
|
await expect(page.getByText(/Ошибка отображения|Minified React error/i)).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admins: superadmin видит список', async ({ page }) => {
|
||||||
|
await page.goto('/admins');
|
||||||
|
await expect(page.getByTestId('page-admins')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('page-forbidden')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('monitoring: страница загружается', async ({ page }) => {
|
||||||
|
await page.goto('/monitoring');
|
||||||
|
await expect(page.getByTestId('page-monitoring')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('logout', async ({ page }) => {
|
||||||
|
await page.getByTestId('menu-user').click();
|
||||||
|
await page.getByTestId('btn-logout').click();
|
||||||
|
await expect(page.getByTestId('page-login')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
+2
-2
@@ -13,8 +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-ui --project=ift-smoke",
|
"test:e2e:ift": "E2E_TARGET=ift playwright test --project=ift-ui",
|
||||||
"test:e2e:stage": "E2E_TARGET=stage playwright test --project=stage-ui --project=stage-smoke"
|
"test:e2e:stage": "E2E_TARGET=stage playwright test --project=stage-ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
|||||||
+13
-17
@@ -10,6 +10,16 @@ const liveChrome = {
|
|||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Mock-suite (CI / локально): preview + page.route mocks. */
|
||||||
|
const mockProject = {
|
||||||
|
name: 'mock',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
testIgnore: [/stand\//, /ift\//],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** IFT/stage после деплоя: живой SPA + admin-api, без mocks. */
|
||||||
|
const standTestMatch = /stand\//;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e/tests',
|
testDir: './e2e/tests',
|
||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
@@ -25,30 +35,16 @@ export default defineConfig({
|
|||||||
locale: 'ru-RU',
|
locale: 'ru-RU',
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
mockProject,
|
||||||
name: 'mock',
|
|
||||||
use: { ...devices['Desktop Chrome'] },
|
|
||||||
testIgnore: /ift\//,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'ift-ui',
|
name: 'ift-ui',
|
||||||
use: { ...liveChrome, baseURL: iftBaseURL },
|
use: { ...liveChrome, baseURL: iftBaseURL },
|
||||||
testIgnore: /ift\//,
|
testMatch: standTestMatch,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ift-smoke',
|
|
||||||
use: { ...liveChrome, baseURL: iftBaseURL },
|
|
||||||
testMatch: /ift\//,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'stage-ui',
|
name: 'stage-ui',
|
||||||
use: { ...liveChrome, baseURL: stageBaseURL },
|
use: { ...liveChrome, baseURL: stageBaseURL },
|
||||||
testIgnore: /ift\//,
|
testMatch: standTestMatch,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'stage-smoke',
|
|
||||||
use: { ...liveChrome, baseURL: stageBaseURL },
|
|
||||||
testMatch: /ift\//,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
webServer: liveTarget
|
webServer: liveTarget
|
||||||
|
|||||||
Reference in New Issue
Block a user