| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- /**
- * Playwright end-to-end smoke test
- *
- * Preconditions (operator must supply):
- * - The backend is running on port 8080 with the built UI served via --webui-dir
- * - SVAPI_ADMIN_KEY env var contains the admin API key (e.g. "e2eadmin")
- * - SVAPI_BASE env var (optional) overrides the base URL (default http://localhost:8080)
- *
- * Run sequence (example):
- * cmake --build build -j"$(nproc)"
- * SMARTBOTIC_VECTORAPI_KEY=e2eadmin ./build/src/smartbotic-vectorapi \
- * --config config/config.json --share-dir api --webui-dir build/webui/dist &
- * cd webui && SVAPI_ADMIN_KEY=e2eadmin SVAPI_BASE=http://localhost:8080 npx playwright test
- *
- * The test creates a collection "e2e_docs" and a document inside it, then
- * deletes the collection (cleanup) before logging out.
- */
- import { test, expect } from '@playwright/test'
- const ADMIN_KEY = process.env['SVAPI_ADMIN_KEY'] ?? 'e2eadmin'
- test('login → collections → documents → logout smoke', async ({ page, context }) => {
- // Monaco mangles typed/inserted JSON via auto-closing brackets, so we paste
- // the document body via the clipboard instead — which needs this permission.
- await context.grantPermissions(['clipboard-read', 'clipboard-write'])
- // Best-effort cleanup so the test is idempotent: a prior failed run may have
- // left the e2e_docs collection behind, which would make the create step fail.
- await page.request.delete('/api/v1/projects/default/collections/e2e_docs', {
- headers: { Authorization: `Bearer ${ADMIN_KEY}` },
- })
- // ── 1. Navigate to / → should redirect to /login ─────────────────────────
- await page.goto('/')
- await expect(page).toHaveURL(/\/login/)
- // ── 2. Fill API key and submit ────────────────────────────────────────────
- await page.locator('[data-testid="api-key-input"]').fill(ADMIN_KEY)
- await page.locator('[data-testid="login-submit-btn"]').click()
- // After login, should land on dashboard (root path)
- await expect(page).toHaveURL(/\/$/)
- // Project selector should be visible and show "default"
- const projectSelector = page.getByRole('combobox', { name: 'Select project' })
- await expect(projectSelector).toBeVisible()
- await expect(projectSelector).toHaveValue('default')
- // ── 3. Navigate to Collections ────────────────────────────────────────────
- // Scope to the sidebar — the Documents page also renders a "Collections"
- // breadcrumb link, so an unscoped role query would match two elements.
- const sidebar = page.locator('aside')
- await sidebar.getByRole('link', { name: 'Collections' }).click()
- await expect(page).toHaveURL(/\/collections/)
- // Open "New collection" modal
- await page.locator('[data-testid="new-collection-btn"]').click()
- // Fill collection name
- const nameInput = page.locator('[data-testid="collection-name-input"]')
- await expect(nameInput).toBeVisible()
- await nameInput.fill('e2e_docs')
- // Kind is already "json" by default — just submit
- await page.locator('[data-testid="collection-create-btn"]').click()
- // Wait for modal to close and collection to appear
- await expect(page.locator('[data-testid="collection-row-e2e_docs"]')).toBeVisible({ timeout: 10_000 })
- // ── 4. Click the collection row → navigates to its documents ──────────────
- await page.locator('[data-testid="collection-row-e2e_docs"]').click()
- await expect(page).toHaveURL(/\/documents\?collection=e2e_docs/)
- // Collection picker should be pre-selected from the `?collection=` param
- const collPicker = page.locator('[data-testid="collection-picker"]')
- await expect(collPicker).toBeVisible()
- await expect(collPicker).toHaveValue('e2e_docs')
- // Click "New document"
- const newDocBtn = page.locator('[data-testid="new-doc-btn"]')
- await expect(newDocBtn).toBeEnabled({ timeout: 5_000 })
- await newDocBtn.click()
- // Monaco editor: wait for it to load and type the document
- // Monaco renders a contenteditable div; we use keyboard to replace the default "{}"
- const monacoEditor = page.locator('.monaco-editor').first()
- await expect(monacoEditor).toBeVisible({ timeout: 15_000 })
- // Click into the editor, select all and replace with our JSON. Pasting via
- // the clipboard avoids Monaco's auto-closing brackets, which corrupt the JSON
- // when typing or inserting character data.
- await monacoEditor.click()
- await page.keyboard.press('Control+a')
- await page.evaluate((t) => navigator.clipboard.writeText(t), '{"name":"e2e"}')
- await page.keyboard.press('Control+v')
- // Save button
- const saveBtn = page.locator('[data-testid="doc-save-btn"]')
- await expect(saveBtn).toBeEnabled({ timeout: 5_000 })
- await saveBtn.click()
- // Document should appear in the list
- await expect(page.locator('[data-testid="doc-row"]').first()).toBeVisible({ timeout: 10_000 })
- // ── 5. Delete the collection (cleanup) ────────────────────────────────────
- await sidebar.getByRole('link', { name: 'Collections' }).click()
- await expect(page).toHaveURL(/\/collections/)
- // Click Delete button for e2e_docs row
- await page.getByRole('button', { name: 'Delete e2e_docs' }).click()
- // Confirm deletion
- await page.locator('[data-testid="confirm-dialog-confirm-btn"]').click()
- // Collection should be gone
- await expect(page.locator('[data-testid="collection-row-e2e_docs"]')).not.toBeVisible({ timeout: 10_000 })
- // ── 6. Logout ──────────────────────────────────────────────────────────────
- await page.locator('[data-testid="logout-btn"]').click()
- await expect(page).toHaveURL(/\/login/)
- })
|