diff --git a/README.md b/README.md index 95041e9..1fb79a0 100644 --- a/README.md +++ b/README.md @@ -137,3 +137,11 @@ just ui-e2e ```bash just staging-smoke https:// ``` + +Для browser-authenticated smoke на реальном стенде: + +```bash +export CRANK_STAGING_ADMIN_EMAIL=owner@example.com +export CRANK_STAGING_ADMIN_PASSWORD=secret +just authenticated-staging-smoke https:// +``` diff --git a/TASKS.md b/TASKS.md index 6a3d324..f1b1ad1 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,14 +2,14 @@ ## Current -### `feat/authenticated-staging-pass` +### `feat/authenticated-staging-smoke-helper` Status: completed DoD: -- authenticated staging pass template exists -- deploy smoke and staging notes docs link to the authenticated pass -- README exposes the authenticated pass doc as the canonical next step after helper smoke +- browser-authenticated staging smoke can be run against a real domain through env vars +- Playwright skips local webServer for remote runs +- docs and justfile expose the authenticated smoke helper ## Next diff --git a/apps/ui/playwright.config.js b/apps/ui/playwright.config.js index badbedf..0b4af18 100644 --- a/apps/ui/playwright.config.js +++ b/apps/ui/playwright.config.js @@ -1,5 +1,8 @@ const { defineConfig, devices } = require('@playwright/test'); +const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3300'; +const skipWebServer = process.env.PLAYWRIGHT_SKIP_WEB_SERVER === '1'; + module.exports = defineConfig({ testDir: './tests/e2e', fullyParallel: false, @@ -13,18 +16,20 @@ module.exports = defineConfig({ ? [['github'], ['html', { open: 'never' }]] : [['list'], ['html', { open: 'never' }]], use: { - baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3300', + baseURL, trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, - webServer: { - command: 'bash scripts/playwright-stack.sh', - cwd: __dirname, - url: (process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3300') + '/login', - timeout: 600_000, - reuseExistingServer: false, - }, + webServer: skipWebServer + ? undefined + : { + command: 'bash scripts/playwright-stack.sh', + cwd: __dirname, + url: `${baseURL}/login`, + timeout: 600_000, + reuseExistingServer: false, + }, projects: [ { name: 'chromium', diff --git a/apps/ui/scripts/playwright-ui-server.js b/apps/ui/scripts/playwright-ui-server.js index 9f03f4b..d0d2685 100644 --- a/apps/ui/scripts/playwright-ui-server.js +++ b/apps/ui/scripts/playwright-ui-server.js @@ -44,6 +44,9 @@ function mapRoute(urlPath) { if (urlPath === '/api-keys') { return path.join(ROOT_DIR, 'html', 'api-keys.html'); } + if (urlPath === '/secrets') { + return path.join(ROOT_DIR, 'html', 'secrets.html'); + } if (urlPath === '/logs') { return path.join(ROOT_DIR, 'html', 'logs.html'); } @@ -156,6 +159,10 @@ const server = http.createServer((request, response) => { redirect(response, '/api-keys'); return; } + if (urlPath === '/html/secrets.html') { + redirect(response, '/secrets'); + return; + } if (urlPath === '/html/logs.html') { redirect(response, '/logs'); return; diff --git a/apps/ui/tests/e2e/staging-authenticated.spec.js b/apps/ui/tests/e2e/staging-authenticated.spec.js new file mode 100644 index 0000000..858d0a4 --- /dev/null +++ b/apps/ui/tests/e2e/staging-authenticated.spec.js @@ -0,0 +1,42 @@ +const { test, expect } = require('@playwright/test'); +const { getSession, localized, login } = require('./helpers'); + +const CORE_PAGES = [ + { path: '/', heading: localized('Operations', 'Операции') }, + { path: '/agents', heading: localized('Agents', 'Агенты') }, + { path: '/api-keys', heading: localized('API Keys', 'API ключи') }, + { path: '/secrets', heading: localized('Secrets', 'Секреты') }, + { path: '/logs', heading: localized('Logs', 'Логи') }, + { path: '/usage', heading: localized('Usage', 'Использование') }, + { path: '/workspace-setup', heading: localized('Workspace', 'Воркспейс') }, + { path: '/settings', heading: localized('Settings', 'Настройки') }, + { path: '/stream-sessions', heading: localized('Stream Sessions', 'Stream Sessions') }, + { path: '/async-jobs', heading: localized('Async Jobs', 'Async Jobs') }, + { path: '/wizard/', heading: localized('Create', 'Создать') }, +]; + +test('authenticated staging smoke logs in and exposes session json', async ({ page }) => { + await login(page); + + const session = await getSession(page); + expect(session.user).toBeTruthy(); + expect(Array.isArray(session.memberships)).toBe(true); + expect(session.memberships.length).toBeGreaterThan(0); +}); + +test('authenticated staging smoke opens core pages without console errors', async ({ page }) => { + const pageErrors = []; + page.on('pageerror', (error) => { + pageErrors.push(error.message); + }); + + await login(page); + + for (const pageSpec of CORE_PAGES) { + await page.goto(pageSpec.path); + await expect(page.locator('#login-form')).toHaveCount(0); + await expect(page.locator('body')).toContainText(pageSpec.heading); + } + + expect(pageErrors).toEqual([]); +}); diff --git a/docs/authenticated-staging-pass.md b/docs/authenticated-staging-pass.md index 17e2f29..080e960 100644 --- a/docs/authenticated-staging-pass.md +++ b/docs/authenticated-staging-pass.md @@ -22,12 +22,22 @@ - свежий deploy уже прошел helper smoke: - `just staging-smoke https://` +- automated browser-authenticated smoke при необходимости запускается так: + - `CRANK_STAGING_ADMIN_EMAIL=... CRANK_STAGING_ADMIN_PASSWORD=... just authenticated-staging-smoke https://` - известны bootstrap admin credentials; - на стенде есть demo data или подготовленный disposable workspace; - браузер открыт с devtools, чтобы фиксировать network/console findings. ## 3. Канонический проход +Перед ручным проходом можно прогнать automated baseline: + +```bash +export CRANK_STAGING_ADMIN_EMAIL=owner@example.com +export CRANK_STAGING_ADMIN_PASSWORD=secret +just authenticated-staging-smoke https:// +``` + ### 3.1. Login and shell 1. Открыть `/login` diff --git a/justfile b/justfile index 9d1f709..651d3fe 100644 --- a/justfile +++ b/justfile @@ -29,3 +29,6 @@ ui-e2e: staging-smoke base_url: bash scripts/staging-smoke.sh {{base_url}} + +authenticated-staging-smoke base_url: + bash scripts/authenticated-staging-smoke.sh {{base_url}} diff --git a/scripts/README.md b/scripts/README.md index 7eda1eb..2249ea2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -9,3 +9,20 @@ Usage: ```bash ./scripts/staging-smoke.sh https://rmcp.itexp.me ``` + +## authenticated-staging-smoke.sh + +Browser-authenticated smoke helper for staging/production-like environments. + +Required environment variables: + +- `CRANK_STAGING_ADMIN_EMAIL` +- `CRANK_STAGING_ADMIN_PASSWORD` + +Usage: + +```bash +CRANK_STAGING_ADMIN_EMAIL=owner@example.com \ +CRANK_STAGING_ADMIN_PASSWORD=secret \ +./scripts/authenticated-staging-smoke.sh https://rmcp.itexp.me +``` diff --git a/scripts/authenticated-staging-smoke.sh b/scripts/authenticated-staging-smoke.sh new file mode 100644 index 0000000..5319e05 --- /dev/null +++ b/scripts/authenticated-staging-smoke.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "usage: $0 " >&2 + echo "example: $0 https://rmcp.itexp.me" >&2 + exit 64 +fi + +if [[ -z "${CRANK_STAGING_ADMIN_EMAIL:-}" ]]; then + echo "CRANK_STAGING_ADMIN_EMAIL is required" >&2 + exit 64 +fi + +if [[ -z "${CRANK_STAGING_ADMIN_PASSWORD:-}" ]]; then + echo "CRANK_STAGING_ADMIN_PASSWORD is required" >&2 + exit 64 +fi + +BASE_URL="${1%/}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +echo "authenticated staging smoke: ${BASE_URL}" + +cd "${REPO_ROOT}/apps/ui" +PLAYWRIGHT_BASE_URL="${BASE_URL}" \ +PLAYWRIGHT_SKIP_WEB_SERVER=1 \ +CRANK_E2E_ADMIN_EMAIL="${CRANK_STAGING_ADMIN_EMAIL}" \ +CRANK_E2E_ADMIN_PASSWORD="${CRANK_STAGING_ADMIN_PASSWORD}" \ +npx playwright test tests/e2e/staging-authenticated.spec.js