/** * mizan E2E Integration Tests * * Real Chromium → Real React app (generated hooks) → Real Django backend * * Every test uses the generated mizan API, not raw call() or fetch(). */ import { test, expect } from '@playwright/test' const BASE = process.env.HARNESS_URL || 'http://localhost:5174' async function fixture(page: any, name: string) { await page.goto(`${BASE}#${name}`) await page.waitForSelector('[data-testid="result"], [data-testid="error-type"]', { timeout: 10000 }) } async function getResult(page: any): Promise { const el = page.locator('[data-testid="result"]') if (await el.count() > 0) return JSON.parse(await el.textContent()) return null } async function getError(page: any) { const typeEl = page.locator('[data-testid="error-type"]') if (await typeEl.count() === 0) return null return { type: await typeEl.textContent(), code: await page.locator('[data-testid="error-code"]').textContent(), message: await page.locator('[data-testid="error-message"]').textContent(), } } // ─── useEcho, useAdd, useMultiply ─────────────────────────────────────────── test.describe('generated function hooks', () => { test('useEcho returns echoed text', async ({ page }) => { await fixture(page, 'echo') const result = await getResult(page) expect(result.message).toContain('e2e-test') }) test('useAdd returns correct sum', async ({ page }) => { await fixture(page, 'add') const result = await getResult(page) expect(result.result).toBe(42) }) test('useMultiply (class-based ServerFunction) returns product', async ({ page }) => { await fixture(page, 'multiply') const result = await getResult(page) expect(result.product).toBe(42) }) test('usePermissionCheckFn succeeds with correct secret', async ({ page }) => { await fixture(page, 'permission-success') const result = await getResult(page) expect(result.message).toBe('access granted') }) }) // ─── Error handling ───────────────────────────────────────────────────────── test.describe('error codes from generated hooks', () => { test('non-existent function → DjangoError NOT_FOUND', async ({ page }) => { await fixture(page, 'not-found') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(error!.code).toBe('NOT_FOUND') }) test('wrong input types → DjangoError VALIDATION_ERROR', async ({ page }) => { await fixture(page, 'validation-error') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(error!.code).toBe('VALIDATION_ERROR') }) test('useWhoami anonymous → auth error', async ({ page }) => { await fixture(page, 'auth-required') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code) }) test('useStaffOnly anonymous → UNAUTHORIZED', async ({ page }) => { await fixture(page, 'staff-only') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code) }) test('useSuperuserOnly anonymous → UNAUTHORIZED', async ({ page }) => { await fixture(page, 'superuser-only') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code) }) test('useVerifiedOnly anonymous → FORBIDDEN', async ({ page }) => { await fixture(page, 'verified-only') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code) }) test('useNotImplementedFn → NOT_IMPLEMENTED', async ({ page }) => { await fixture(page, 'not-implemented') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(error!.code).toBe('NOT_IMPLEMENTED') }) test('useBuggyFn → INTERNAL_ERROR', async ({ page }) => { await fixture(page, 'internal-error') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(error!.code).toBe('INTERNAL_ERROR') }) test('usePermissionCheckFn wrong secret → FORBIDDEN', async ({ page }) => { await fixture(page, 'permission-error') const error = await getError(page) expect(error!.type).toBe('DjangoError') expect(error!.code).toBe('FORBIDDEN') }) }) // ─── Context hooks ────────────────────────────────────────────────────────── test.describe('generated context hooks', () => { test('useCurrentUser returns anonymous data', async ({ page }) => { await page.goto(`${BASE}#context-current-user`) // Context loads async, wait for result await page.waitForSelector('[data-testid="result"]', { timeout: 10000 }) const result = await getResult(page) expect(result.authenticated).toBe(false) expect(result.email).toBe('') }) }) // ─── Form hooks ───────────────────────────────────────────────────────────── test.describe('generated form hooks', () => { test('useLoginForm loads schema with field definitions', async ({ page }) => { await fixture(page, 'form-login-schema') const result = await getResult(page) expect(result.fields).toBeDefined() expect(result.fields.login).toBeDefined() expect(result.fields.password).toBeDefined() }) test('useContactForm loads schema with mizanFormMeta', async ({ page }) => { await fixture(page, 'form-contact-schema') const result = await getResult(page) expect(result.title).toBe('Contact Us') expect(result.subtitle).toBe("We'd love to hear from you") expect(result.submit_label).toBe('Send Message') expect(result.meta.live_validation).toBe(true) }) test('useContactForm submit returns on_submit_success data', async ({ page }) => { await fixture(page, 'form-contact-submit') const result = await getResult(page) expect(result.success).toBe(true) expect(result.data.received).toBe(true) expect(result.data.from).toBe('test@example.com') }) }) // ─── Channel hooks ────────────────────────────────────────────────────────── test.describe('generated channel hooks', () => { test('useChatChannel receives echoed message', async ({ page }) => { await page.goto(`${BASE}#channel-chat`) await page.waitForFunction( () => { const el = document.querySelector('[data-testid="channel-message-count"]') return el && parseInt(el.textContent || '0') > 0 }, { timeout: 15000 } ) const msg = JSON.parse(await page.locator('[data-testid="channel-last-message"]').textContent()) expect(msg.text).toBe('hello from e2e') }) })