MAI CALL - step 13
pnpm test:e2e verde. Passo 13 completo.
O que foi feito:
Polish:
SyncProvider — dead-letter toast fixo na base do ecrã (cor destructive, botão ✕ para fechar), dispara quando broadcast({ type: 'dead-letter' }) chega via BroadcastChannel
Loading states e empty states já estavam implementados nos passos anteriores
E2E test — e2e/tests/mai-call.spec.ts:
Substitui o ping.spec.ts obsoleto
Arranca ambos os servidores (operator-pwa :3000 + admin-web :3001) com AUTH_DEV_AUTOLOGIN=true
Fluxo completo em 13.7s: formulário → IndexedDB → sync automático → admin queue → claim (OPEN→CLAIMED) → enable RESOLVED filter → resolve dialog → confirm (CLAIMED→RESOLVED)
pnpm test:e2e passa ✓
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_BASE } from '../playwright.config';
|
||||
|
||||
/**
|
||||
* Happy-path E2E for MAI CALL v0.1:
|
||||
* operator creates request → admin claims → admin resolves
|
||||
*
|
||||
* Preconditions (met by `pnpm db:seed` + running docker compose):
|
||||
* - Demo Factory tenant with workstations CTR04, QVN_RTL_2, MTG_01
|
||||
* - admin@demo.local with role ADMIN
|
||||
* - AUTH_DEV_AUTOLOGIN=true on both servers (set in playwright.config.ts)
|
||||
*/
|
||||
test('MAI CALL happy path: create → claim → resolve', async ({ page, context }) => {
|
||||
// Use a unique description so the test can find its own card in the queue.
|
||||
const desc = `E2E ${Date.now()} — ruído anormal no posto`;
|
||||
|
||||
// ── 1. Operator creates a request ────────────────────────────────────────
|
||||
await page.goto('/maintenance/new');
|
||||
|
||||
// Wait for workstation options to load (select is disabled while loading)
|
||||
await expect(page.locator('#workstation')).toBeEnabled({ timeout: 15_000 });
|
||||
|
||||
// Select the first real workstation
|
||||
const firstOpt = page.locator('#workstation option:not([value=""])').first();
|
||||
const wsValue = await firstOpt.getAttribute('value');
|
||||
await page.selectOption('#workstation', wsValue!);
|
||||
|
||||
await page.fill('#description', desc);
|
||||
await page.click('button[type=submit]');
|
||||
|
||||
// ── 2. Wait for the sync to complete ─────────────────────────────────────
|
||||
// The form queues to IndexedDB; the SyncProvider immediately syncs when
|
||||
// online. The sent page changes from "Em fila" → "Pedido enviado".
|
||||
await page.waitForURL('**/maintenance/sent**');
|
||||
await expect(page.locator('h1')).toHaveText('Pedido enviado', { timeout: 30_000 });
|
||||
|
||||
// ── 3. Admin queue shows the request ─────────────────────────────────────
|
||||
const adminPage = await context.newPage();
|
||||
await adminPage.goto(`${ADMIN_BASE}/maintenance`);
|
||||
|
||||
// Find the card that contains our description (admin polls every 5s)
|
||||
const card = adminPage
|
||||
.locator('[data-testid="request-card"]')
|
||||
.filter({ hasText: desc.slice(0, 40) });
|
||||
await expect(card).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Card starts as OPEN
|
||||
await expect(card.locator('span', { hasText: 'Aberto' })).toBeVisible();
|
||||
|
||||
// ── 4. Admin claims the request ───────────────────────────────────────────
|
||||
await card.locator('button', { hasText: 'Aceitar' }).click();
|
||||
|
||||
// Card changes to CLAIMED
|
||||
await expect(card.locator('span', { hasText: 'Em curso' })).toBeVisible({ timeout: 10_000 });
|
||||
await expect(card.locator('button', { hasText: 'Aceitar' })).not.toBeVisible();
|
||||
|
||||
// ── 5. Enable RESOLVED filter so the card stays visible after resolve ─────
|
||||
await adminPage.locator('label', { hasText: 'Resolvido' }).click();
|
||||
|
||||
// ── 6. Admin resolves the request ────────────────────────────────────────
|
||||
await card.locator('button', { hasText: 'Marcar resolvido' }).click();
|
||||
|
||||
const dialog = adminPage.locator('h2', { hasText: 'Marcar como resolvido' });
|
||||
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
await adminPage.locator('button', { hasText: 'Confirmar' }).click();
|
||||
|
||||
// Card changes to RESOLVED
|
||||
await expect(card.locator('span', { hasText: 'Resolvido' })).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// No action buttons remain on RESOLVED card
|
||||
await expect(card.locator('button', { hasText: 'Aceitar' })).not.toBeVisible();
|
||||
await expect(card.locator('button', { hasText: 'Marcar resolvido' })).not.toBeVisible();
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('ping smoke', () => {
|
||||
test('home page shows the seeded Demo Factory tenant', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// RSC card with the server-side ping result.
|
||||
const success = page.getByTestId('ping-success');
|
||||
await expect(success).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
const tenant = page.getByTestId('tenant-name');
|
||||
await expect(tenant).toHaveText('Demo Factory');
|
||||
|
||||
// Client-side hook hitting the same procedure via /api/trpc.
|
||||
const clientTenant = page.getByTestId('ping-client-tenant');
|
||||
await expect(clientTenant).toContainText('Demo Factory');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user