Wiki/e2e/tests/database-view-realtime-sse.spec.ts
Corentin JOGUET e9695450ef
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
E2E Playwright / Playwright e2e (chromium) (push) Waiting to run
test(e2e): add Playwright cross-stack tests for R3.1.e database-view
7 scenarios covering the full bridge+DocAdenice+Baserow chain:
auth login, database-view insert, inline edit persistence, SSE realtime
update (no reload), RBAC write-denied, kanban drag-drop, calendar reschedule.

Includes docker-compose.e2e.yml (Postgres+Redis+Baserow+bridge+DocAdenice),
playwright.config.ts (3 projects: chromium/firefox/webkit), auth+baserow+cleanup
fixtures, global setup (API login + Baserow seed), and GitHub Actions e2e.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 00:37:23 +02:00

104 lines
3.8 KiB
TypeScript

/**
* Scenario: database-view-realtime-sse
*
* Verifies the full SSE end-to-end flow:
* 1. Browser A has a page open with a database-view (grid).
* 2. An external API call (via Baserow API + bridge webhook) modifies a row.
* 3. Browser A sees the updated value without any manual reload.
*
* This test exercises the full chain:
* Baserow webhook -> bridge handler -> Redis Streams -> SSE endpoint
* -> useDatabaseRealtimeUpdates hook -> React Query invalidation -> re-render.
*
* SSE propagation timing: the bridge polls Redis every 100ms (default). We
* allow up to 15 seconds for the update to appear — well above the 99th
* percentile for local network.
*/
import { test, expect } from "@playwright/test";
import * as fs from "fs";
import * as path from "path";
import type { BaserowSeed } from "../fixtures/baserow";
import { updateRowViaBaserowApi } from "../fixtures/baserow";
const BASE_URL = process.env.E2E_DOCMOST_URL ?? "http://localhost:5173";
const SEED_FILE = path.resolve(__dirname, "../.auth/baserow-seed.json");
const SSE_TEST_VALUE = "SSE Updated Task";
test.describe("database-view realtime SSE", () => {
let seed: BaserowSeed;
test.beforeAll(() => {
if (!fs.existsSync(SEED_FILE)) {
throw new Error(`Seed file not found at ${SEED_FILE}.`);
}
seed = JSON.parse(fs.readFileSync(SEED_FILE, "utf-8")) as BaserowSeed;
});
test(
"row update via Baserow API propagates to open browser via SSE without reload",
async ({ page, request }) => {
// Open a page with a database-view node (depends on insert spec having run).
await page.goto(BASE_URL);
const tableRenderer = page
.getByTestId("table-renderer")
.or(page.locator("[data-node-type='database-view'] table"))
.first();
const rendererVisible = await tableRenderer
.isVisible({ timeout: 5_000 })
.catch(() => false);
if (!rendererVisible) {
test.skip(
true,
"No database-view page found. Run database-view-insert spec first.",
);
return;
}
// Confirm the initial value is present.
await expect(page.getByText("Task Beta")).toBeVisible({ timeout: 10_000 });
// Verify the SSE connection is established by checking the bridge events endpoint.
// We do this by intercepting XHR/fetch — but SSE uses EventSource natively.
// Instead we monitor the page console for the SSE open event log.
// The bridge logs "SSE client connected" on open — this doesn't leak to the client.
// We rely purely on the observable DOM change after the Baserow API call.
// Wait a moment to ensure the SSE connection is established and stable.
await page.waitForTimeout(2_000);
// Modify row 2 ("Task Beta") via the Baserow API directly.
// This bypasses the bridge write path — Baserow will emit a webhook to the bridge,
// which publishes to Redis Streams, which the SSE connection picks up.
const rowIdToUpdate = seed.rowIds[1]; // Task Beta
await updateRowViaBaserowApi(request, seed.token, seed.tableId, rowIdToUpdate, {
[seed.primaryFieldName]: SSE_TEST_VALUE,
});
// The browser should receive the SSE event and invalidate the React Query cache,
// causing the table to re-render with the updated value — WITHOUT a page reload.
await expect(page.getByText(SSE_TEST_VALUE)).toBeVisible({
timeout: 15_000,
});
// The old value "Task Beta" must be gone from the rendered table.
await expect(page.getByRole("cell", { name: "Task Beta" })).toBeHidden({
timeout: 5_000,
});
},
);
test.afterAll(async ({ request }) => {
// Restore "Task Beta".
if (!seed?.rowIds[1]) return;
await updateRowViaBaserowApi(request, seed.token, seed.tableId, seed.rowIds[1], {
[seed.primaryFieldName]: "Task Beta",
});
});
});