Files
bitvid/tests/e2e/agent-testability.spec.ts

246 lines
6.6 KiB
TypeScript

/**
* Agent testability E2E tests.
*
* These tests demonstrate that bitvid is fully testable by Playwright CLI agents.
* They exercise:
* 1. Test harness installation and relay override
* 2. Programmatic login (bypassing the UI)
* 3. Seeding test data into a local mock relay
* 4. Inspecting app state through the harness API
* 5. Key data-testid selectors for UI interaction
*/
import { test, expect } from "./helpers/bitvidTestFixture";
test.describe("Agent testability infrastructure", () => {
test("test harness installs when __test__=1 is set", async ({
page,
relay,
gotoApp,
}) => {
await gotoApp();
const harness = await page.evaluate(() => {
const h = (window as any).__bitvidTest__;
return h ? { version: h.version } : null;
});
expect(harness).not.toBeNull();
expect(harness!.version).toBe(2);
});
test("relay overrides are applied via query params", async ({
page,
relay,
gotoApp,
relayUrl,
}) => {
await gotoApp();
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
// Relay overrides should include our test relay
expect(state.relays).not.toBeNull();
if (state.relays) {
const allRelays = state.relays.all || [];
expect(allRelays).toContain(relayUrl);
}
});
test("setTestRelays aligns relayManager and nostrClient", async ({
page,
gotoApp,
relayUrl,
}) => {
await gotoApp();
const result = await page.evaluate((url) => {
const harness = (window as any).__bitvidTest__;
if (!harness || typeof harness.setTestRelays !== "function") {
throw new Error("setTestRelays missing");
}
return harness.setTestRelays([url]);
}, relayUrl);
expect(result.ok).toBe(true);
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.relays.read).toEqual([relayUrl]);
expect(state.relayManager.read).toEqual([relayUrl]);
});
test("programmatic login works via test harness", async ({
page,
relay,
gotoApp,
loginAs,
testPubkey,
}) => {
await gotoApp();
const pubkey = await loginAs(page);
expect(pubkey).toBe(testPubkey);
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.isLoggedIn).toBe(true);
expect(state.activePubkey).toBe(testPubkey);
});
test("can seed events into mock relay and retrieve them", async ({
relay,
seedEvent,
clearRelay,
}) => {
await clearRelay();
await seedEvent({
title: "Test Video Alpha",
url: "https://example.com/alpha.mp4",
dTag: "alpha-001",
});
await seedEvent({
title: "Test Video Beta",
url: "https://example.com/beta.mp4",
magnet: "magnet:?xt=urn:btih:0000000000000000000000000000000000000000",
dTag: "beta-001",
});
// Verify via HTTP API
const httpUrl = (relay as any).httpUrl;
const resp = await fetch(`${httpUrl}/events`);
const events = await resp.json();
expect(events.length).toBe(2);
});
test("relay health endpoint works", async ({ relay }) => {
const httpUrl = (relay as any).httpUrl;
const resp = await fetch(`${httpUrl}/health`);
const health = await resp.json();
expect(health.ok).toBe(true);
expect(typeof health.eventCount).toBe("number");
expect(typeof health.connectionCount).toBe("number");
});
});
test.describe("Data-testid selectors are present", () => {
test("critical UI elements have data-testid attributes", async ({
page,
relay,
gotoApp,
}) => {
await gotoApp();
// Header controls
await expect(page.locator('[data-testid="login-button"]')).toBeVisible();
await expect(page.locator('[data-testid="search-input"]')).toBeVisible();
// Upload button is hidden until logged in — verify it exists in DOM
const uploadBtn = page.locator('[data-testid="upload-button"]');
await expect(uploadBtn).toBeAttached();
});
test("login modal has testid selectors", async ({
page,
relay,
gotoApp,
}) => {
await gotoApp();
// Open the login modal
await page.locator('[data-testid="login-button"]').click();
// Wait for modal to transition into an open, visible state.
await page.waitForFunction(() => {
const modal = document.querySelector('[data-testid="login-modal"]');
if (!(modal instanceof HTMLElement)) {
return false;
}
const openAttr = modal.getAttribute("data-open");
return openAttr === "true" && !modal.classList.contains("hidden");
}, { timeout: 15000 });
// Check provider buttons exist
const providerButtons = page.locator(
'[data-testid="login-provider-button"]',
);
// At least one provider button should be present
await expect(providerButtons.first()).toBeVisible({ timeout: 15000 });
});
});
test.describe("App state inspection", () => {
test("getAppState reports logged-out state by default", async ({
page,
relay,
gotoApp,
}) => {
await gotoApp();
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.isLoggedIn).toBe(false);
expect(state.activePubkey).toBeNull();
});
test("logout clears active signer", async ({
page,
relay,
gotoApp,
loginAs,
}) => {
await gotoApp();
await loginAs(page);
let state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.isLoggedIn).toBe(true);
await page.evaluate(() => {
(window as any).__bitvidTest__.logout();
});
state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.isLoggedIn).toBe(false);
});
test("list sync event capture APIs are available", async ({
page,
gotoApp,
}) => {
await gotoApp();
const result = await page.evaluate(() => {
const harness = (window as any).__bitvidTest__;
if (!harness) {
throw new Error("Harness missing");
}
harness.clearListSyncEvents();
window.dispatchEvent(
new CustomEvent("bitvid:auth-loading-state", {
detail: { lists: "loading", listsDetail: { ready: false } },
}),
);
const events = harness.getListSyncEvents();
return {
count: events.length,
firstSource: events[0]?.source || "",
};
});
expect(result.count).toBeGreaterThan(0);
expect(result.firstSource).toBe("auth-loading-state");
});
});