From 7ea8fde379aaafed7ace8cfb1735466e23de1e8d Mon Sep 17 00:00:00 2001 From: Brother Darryl <146fb160a3266e6165bfa385f6048c975eda9e21cf65da097a0b5ea7952532a5@buzz.block.builderlab.xyz> Date: Tue, 18 Aug 2026 10:56:16 -0400 Subject: [PATCH] test(e2e): require explicit origin bootstrap Signed-off-by: Brother Darryl <146fb160a3266e6165bfa385f6048c975eda9e21cf65da097a0b5ea7952532a5@buzz.block.builderlab.xyz> --- desktop/package.json | 5 +- desktop/scripts/check-e2e-bootstrap.mjs | 24 +++ .../tests/e2e/active-turn-resilience.spec.ts | 4 +- .../activity-scope-label-screenshots.spec.ts | 7 +- .../e2e/add-community-screenshots.spec.ts | 4 +- .../tests/e2e/agent-access-warning.spec.ts | 10 +- .../e2e/agent-error-state-screenshots.spec.ts | 4 +- .../e2e/agent-lifecycle-feedback.spec.ts | 6 +- .../tests/e2e/agent-numeric-tuning.spec.ts | 8 +- .../e2e/agent-provider-dropdowns.spec.ts | 8 +- .../e2e/agent-readiness-screenshots.spec.ts | 6 +- .../e2e/agent-snapshot-recipient.spec.ts | 4 +- desktop/tests/e2e/agents.spec.ts | 4 +- desktop/tests/e2e/animated-avatar.spec.ts | 4 +- desktop/tests/e2e/badge.spec.ts | 36 ++-- desktop/tests/e2e/boot-splash.spec.ts | 6 +- desktop/tests/e2e/bootstrap.spec.ts | 30 ++-- .../tests/e2e/buzz-theme-screenshots.spec.ts | 12 +- .../e2e/channel-activity-popover.spec.ts | 12 +- .../tests/e2e/channel-add-screenshots.spec.ts | 10 +- desktop/tests/e2e/channel-browser.spec.ts | 16 +- .../e2e/channel-composer-overflow.spec.ts | 12 +- desktop/tests/e2e/channel-controls.spec.ts | 4 +- .../e2e/channel-dense-second-reach.spec.ts | 4 +- desktop/tests/e2e/channel-mute.spec.ts | 14 +- .../channel-shared-header-backdrop.spec.ts | 4 +- desktop/tests/e2e/channel-sort.spec.ts | 4 +- desktop/tests/e2e/channel-star.spec.ts | 10 +- .../e2e/channel-window-mock-paging.spec.ts | 6 +- desktop/tests/e2e/channels.spec.ts | 164 +++++++++--------- .../tests/e2e/cold-switch-longtask.perf.ts | 4 +- desktop/tests/e2e/community-rail.spec.ts | 50 +++--- desktop/tests/e2e/composer-image-draw.spec.ts | 8 +- .../tests/e2e/composer-link-shortcut.spec.ts | 4 +- .../e2e/composer-selection-formatting.spec.ts | 10 +- .../e2e/composer-tooltip-dismiss.spec.ts | 8 +- .../e2e/config-bridge-screenshots.spec.ts | 6 +- .../tests/e2e/core-memory-screenshots.spec.ts | 4 +- desktop/tests/e2e/custom-emoji-ui.spec.ts | 8 +- desktop/tests/e2e/custom-emoji.spec.ts | 4 +- desktop/tests/e2e/deep-link-invite.spec.ts | 20 +-- .../tests/e2e/dm-double-notification.spec.ts | 4 +- .../e2e/dm-history-live-regression.spec.ts | 10 +- .../e2e/dm-new-message-screenshots.spec.ts | 8 +- .../tests/e2e/doctor-cta-screenshots.spec.ts | 10 +- desktop/tests/e2e/doctor-states.spec.ts | 32 ++-- desktop/tests/e2e/drafts-screenshots.spec.ts | 6 +- desktop/tests/e2e/edit-agent-run-on.spec.ts | 4 +- desktop/tests/e2e/edit-agent.spec.ts | 8 +- desktop/tests/e2e/empty-edit-delete.spec.ts | 4 +- .../e2e/entity-link-recipient-cards.spec.ts | 10 +- desktop/tests/e2e/file-attachment.spec.ts | 30 ++-- ...reground-responsiveness-regression.spec.ts | 4 +- .../global-agent-config-screenshots.spec.ts | 12 +- .../e2e/harness-catalog-screenshots.spec.ts | 4 +- desktop/tests/e2e/harness-management.spec.ts | 6 +- .../e2e/home-collapsed-top-chrome.spec.ts | 4 +- ...d-communities-settings-screenshots.spec.ts | 4 +- .../tests/e2e/huddle-transcription.spec.ts | 54 +++--- .../e2e/human-edit-agent-content.spec.ts | 14 +- .../tests/e2e/identity-archive-hide.spec.ts | 20 +-- desktop/tests/e2e/identity-archive.spec.ts | 6 +- desktop/tests/e2e/identity-key-help.spec.ts | 6 +- desktop/tests/e2e/identity-lost.spec.ts | 32 ++-- .../e2e/image-attachment-gallery.spec.ts | 22 +-- desktop/tests/e2e/inbox-edit.spec.ts | 16 +- desktop/tests/e2e/inbox-live-update.spec.ts | 16 +- desktop/tests/e2e/inbox-reactions.spec.ts | 4 +- .../e2e/inbox-refactor-screenshots.spec.ts | 10 +- .../tests/e2e/inline-custom-harness.spec.ts | 8 +- desktop/tests/e2e/integration.spec.ts | 18 +- desktop/tests/e2e/invite-link-copy.spec.ts | 10 +- .../e2e/invites-settings-screenshots.spec.ts | 4 +- desktop/tests/e2e/key-import-reveal.spec.ts | 4 +- .../e2e/live-broadcast-reply-timeline.spec.ts | 4 +- .../e2e/local-archive-screenshots.spec.ts | 4 +- .../tests/e2e/markdown-parse-cache.spec.ts | 4 +- desktop/tests/e2e/mentions.spec.ts | 138 +++++++-------- desktop/tests/e2e/mesh-compute.spec.ts | 6 +- .../e2e/message-feedback-snapshots.spec.ts | 6 +- desktop/tests/e2e/messaging.spec.ts | 126 +++++++------- desktop/tests/e2e/mobile-pairing-qr.spec.ts | 10 +- desktop/tests/e2e/navigation.spec.ts | 42 +++-- .../e2e/needs-restart-screenshots.spec.ts | 4 +- desktop/tests/e2e/nostr-bind.spec.ts | 10 +- .../tests/e2e/observer-archive-policy.spec.ts | 8 +- .../e2e/observer-feed-screenshots.spec.ts | 4 +- .../e2e/onboarding-agent-defaults.spec.ts | 52 +++--- .../tests/e2e/onboarding-avatar-skip.spec.ts | 14 +- desktop/tests/e2e/onboarding-backup.spec.ts | 8 +- .../onboarding-docked-cta-screenshots.spec.ts | 10 +- desktop/tests/e2e/onboarding.spec.ts | 132 +++++++------- desktop/tests/e2e/overscroll-boundary.spec.ts | 6 +- .../tests/e2e/parity-ancestor-island.spec.ts | 4 +- .../e2e/persistent-agent-audience.spec.ts | 7 +- desktop/tests/e2e/persona-env-vars.spec.ts | 4 +- ...persona-model-combobox-screenshots.spec.ts | 4 +- desktop/tests/e2e/persona-sync.spec.ts | 4 +- desktop/tests/e2e/profile-active-turn.spec.ts | 4 +- .../tests/e2e/profile-backup-settings.spec.ts | 4 +- .../e2e/profile-custom-emoji-status.spec.ts | 4 +- desktop/tests/e2e/profile-nsec-reveal.spec.ts | 6 +- desktop/tests/e2e/profile.spec.ts | 66 +++---- .../tests/e2e/project-commit-detail.spec.ts | 27 +-- desktop/tests/e2e/project-inbox.spec.ts | 4 +- .../tests/e2e/project-issue-comments.spec.ts | 4 +- desktop/tests/e2e/project-pr-review.spec.ts | 22 +-- .../tests/e2e/projects-v3-screenshots.spec.ts | 6 +- .../e2e/pubkey-display-screenshots.spec.ts | 10 +- desktop/tests/e2e/reaction-names.spec.ts | 6 +- desktop/tests/e2e/reaction-order.spec.ts | 4 +- desktop/tests/e2e/relay-connectivity.spec.ts | 14 +- .../e2e/relay-reconnect-affordance.spec.ts | 6 +- desktop/tests/e2e/relay-reconnect.spec.ts | 30 ++-- desktop/tests/e2e/relay-restart.live.spec.ts | 4 +- desktop/tests/e2e/release-smoke.spec.ts | 10 +- .../tests/e2e/reminder-click-repro.spec.ts | 6 +- desktop/tests/e2e/reminders.spec.ts | 8 +- desktop/tests/e2e/scroll-history.spec.ts | 38 ++-- desktop/tests/e2e/scroll-smoothness.perf.ts | 6 +- desktop/tests/e2e/scrollback-buzzbugs.perf.ts | 4 +- .../e2e/search-scope-screenshots.spec.ts | 4 +- .../tests/e2e/send-channel-binding.spec.ts | 6 +- .../tests/e2e/settings-section-layout.spec.ts | 6 +- .../e2e/sidebar-more-unread-overlap.spec.ts | 4 +- .../tests/e2e/sidebar-offcanvas-rail.spec.ts | 4 +- desktop/tests/e2e/sidebar-relay-card.spec.ts | 24 +-- desktop/tests/e2e/sidebar-snapshot.spec.ts | 22 +-- desktop/tests/e2e/sidebar.spec.ts | 32 ++-- .../tests/e2e/signout-confirmation.spec.ts | 10 +- desktop/tests/e2e/signout-screenshots.spec.ts | 6 +- desktop/tests/e2e/smoke.spec.ts | 52 +++--- desktop/tests/e2e/spoiler.spec.ts | 16 +- desktop/tests/e2e/stream.spec.ts | 18 +- desktop/tests/e2e/team-mentions.spec.ts | 4 +- desktop/tests/e2e/team-snapshot.spec.ts | 4 +- desktop/tests/e2e/terminal-wheel.spec.ts | 4 +- desktop/tests/e2e/thread-focus-mode.spec.ts | 6 +- .../e2e/thread-reply-anchor-roleplay.spec.ts | 4 +- desktop/tests/e2e/thread-unread.spec.ts | 28 +-- .../tests/e2e/threadpane-ultrawide.spec.ts | 4 +- desktop/tests/e2e/timeline-no-shift.spec.ts | 12 +- .../e2e/top-chrome-zoom-clearance.spec.ts | 8 +- desktop/tests/e2e/typing-latency.perf.ts | 4 +- desktop/tests/e2e/unread-pill.spec.ts | 10 +- desktop/tests/e2e/video-attachment.spec.ts | 22 +-- desktop/tests/e2e/virtualization.spec.ts | 31 ++-- desktop/tests/e2e/voice-settings.spec.ts | 12 +- .../tests/e2e/warm-switch-markdown.perf.ts | 4 +- .../welcome-agent-modal-screenshots.spec.ts | 8 +- desktop/tests/e2e/where-to-run-config.spec.ts | 4 +- desktop/tests/e2e/workflows.spec.ts | 4 +- desktop/tests/helpers/bootstrap.ts | 12 +- desktop/tests/helpers/test.ts | 39 +---- web/package.json | 5 +- web/scripts/check-e2e-bootstrap.mjs | 24 +++ web/tests/e2e/bootstrap.spec.ts | 30 ++-- web/tests/e2e/smoke.spec.ts | 10 +- web/tests/helpers/bootstrap.ts | 12 +- web/tests/helpers/test.ts | 39 +---- 160 files changed, 1237 insertions(+), 1187 deletions(-) create mode 100644 desktop/scripts/check-e2e-bootstrap.mjs create mode 100644 web/scripts/check-e2e-bootstrap.mjs diff --git a/desktop/package.json b/desktop/package.json index 683c1529d2..8caad77952 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -12,7 +12,7 @@ "check:px-text": "node ./scripts/check-px-text.mjs", "check:pubkey-truncation": "node ./scripts/check-pubkey-truncation.mjs", "lint": "biome lint .", - "check": "biome check . && pnpm check:px-text && pnpm check:pubkey-truncation", + "check": "biome check . && pnpm check:px-text && pnpm check:e2e-bootstrap && pnpm check:pubkey-truncation", "format": "biome format --write .", "test": "node --import ./test-loader.mjs --experimental-strip-types --test \"src/**/*.test.mjs\"", "preview": "vite preview", @@ -22,7 +22,8 @@ "test:e2e:integration": "pnpm build:e2e && playwright test --project=integration", "test:e2e:release-smoke": "pnpm build:e2e && playwright test --config=playwright.release-smoke.config.ts", "test:e2e:report": "playwright show-report", - "tauri:build": "tauri build" + "tauri:build": "tauri build", + "check:e2e-bootstrap": "node ./scripts/check-e2e-bootstrap.mjs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/desktop/scripts/check-e2e-bootstrap.mjs b/desktop/scripts/check-e2e-bootstrap.mjs new file mode 100644 index 0000000000..47329962bd --- /dev/null +++ b/desktop/scripts/check-e2e-bootstrap.mjs @@ -0,0 +1,24 @@ +import fs from "node:fs"; +import path from "node:path"; + +const projectRoot = path.resolve(import.meta.dirname, ".."); +const specs = fs + .readdirSync(path.join(projectRoot, "tests/e2e")) + .filter((name) => /\.(spec|perf)\.ts$/.test(name)); +const exempt = new Set(["agents-everywhere.live.spec.ts"]); +const violations = []; +for (const name of specs) { + if (exempt.has(name)) continue; + const source = fs.readFileSync( + path.join(projectRoot, "tests/e2e", name), + "utf8", + ); + if (!source.includes('from "../helpers/test"')) continue; + if (!/await\s+bootstrapE2ePage\(/.test(source)) violations.push(name); +} +if (violations.length) { + console.error( + `E2E specs must register bootstrapE2ePage after their setup:\n${violations.join("\n")}`, + ); + process.exit(1); +} diff --git a/desktop/tests/e2e/active-turn-resilience.spec.ts b/desktop/tests/e2e/active-turn-resilience.spec.ts index d338095910..1e9c061473 100644 --- a/desktop/tests/e2e/active-turn-resilience.spec.ts +++ b/desktop/tests/e2e/active-turn-resilience.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -35,7 +35,7 @@ async function waitForBridge(page: import("@playwright/test").Page) { } async function openAgentsView(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForBridge(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("unified-agents-groups")).toBeVisible({ diff --git a/desktop/tests/e2e/activity-scope-label-screenshots.spec.ts b/desktop/tests/e2e/activity-scope-label-screenshots.spec.ts index 89c72384dd..614302a846 100644 --- a/desktop/tests/e2e/activity-scope-label-screenshots.spec.ts +++ b/desktop/tests/e2e/activity-scope-label-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -17,7 +17,7 @@ async function openActivityFromChannel( channelTestId: string, channelTitle: string, ) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId(channelTestId).click(); await expect(page.getByTestId("chat-title")).toHaveText(channelTitle); @@ -118,7 +118,8 @@ test.describe("activity panel scope label", () => { ], }); - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${AGENTS_CHANNEL_ID}?agentSession=${AGENT_PUBKEY}`, { waitUntil: "domcontentloaded" }, ); diff --git a/desktop/tests/e2e/add-community-screenshots.spec.ts b/desktop/tests/e2e/add-community-screenshots.spec.ts index af0a2a4b28..823e3d15f3 100644 --- a/desktop/tests/e2e/add-community-screenshots.spec.ts +++ b/desktop/tests/e2e/add-community-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { test } from "../helpers/test"; +import { test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -41,7 +41,7 @@ test.beforeEach(async ({ page }) => { skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-rail-add").click(); }); diff --git a/desktop/tests/e2e/agent-access-warning.spec.ts b/desktop/tests/e2e/agent-access-warning.spec.ts index 79d4b81ee3..8cabd49d4d 100644 --- a/desktop/tests/e2e/agent-access-warning.spec.ts +++ b/desktop/tests/e2e/agent-access-warning.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -50,7 +50,7 @@ test("open agent access explains the available access before save", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("channel-members-trigger").click(); const accessBadge = page.getByTestId( @@ -181,7 +181,7 @@ test("full agent editor tightens the exact sidebar agent instance", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("channel-members-trigger").click(); const accessBadge = page.getByTestId( @@ -272,7 +272,7 @@ test("a provider-backed agent's warning names the server, not this computer", as }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openAgentAccessDialog(page, agent.pubkey); await page.getByTestId("agent-respond-to-select").selectOption("anyone"); @@ -299,7 +299,7 @@ test("persona-backed edit warns before saving open access", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByRole("button", { name: "Tyler Agent agent profile" }).click(); await page.getByTestId("user-profile-edit-agent").click(); diff --git a/desktop/tests/e2e/agent-error-state-screenshots.spec.ts b/desktop/tests/e2e/agent-error-state-screenshots.spec.ts index 17b5923298..27087b39db 100644 --- a/desktop/tests/e2e/agent-error-state-screenshots.spec.ts +++ b/desktop/tests/e2e/agent-error-state-screenshots.spec.ts @@ -13,7 +13,7 @@ * which ARE reachable in the current build. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -39,7 +39,7 @@ const GENERIC_ERROR_AGENT = { }; async function gotoAgentsView(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("open-agents-view")).toBeVisible({ timeout: 10_000, }); diff --git a/desktop/tests/e2e/agent-lifecycle-feedback.spec.ts b/desktop/tests/e2e/agent-lifecycle-feedback.spec.ts index 7a05e8fd51..3a7868ae40 100644 --- a/desktop/tests/e2e/agent-lifecycle-feedback.spec.ts +++ b/desktop/tests/e2e/agent-lifecycle-feedback.spec.ts @@ -9,7 +9,7 @@ * the old "Running agents keep their current settings…" text is gone. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -29,7 +29,7 @@ const CASCADE_AGENT_B_PUBKEY = "bb".repeat(32); * Navigate to the Agents view and wait for its unified list to mount. */ async function openAgentsView(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("unified-agents-groups")).toBeVisible({ timeout: 10_000, @@ -37,7 +37,7 @@ async function openAgentsView(page: import("@playwright/test").Page) { } async function openAiDefaultsSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); diff --git a/desktop/tests/e2e/agent-numeric-tuning.spec.ts b/desktop/tests/e2e/agent-numeric-tuning.spec.ts index 1043954fa6..5676b68678 100644 --- a/desktop/tests/e2e/agent-numeric-tuning.spec.ts +++ b/desktop/tests/e2e/agent-numeric-tuning.spec.ts @@ -15,13 +15,13 @@ * visible as generic rows (never the "unsupported" empty state). */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; // ── Helpers ──────────────────────────────────────────────────────────────── async function openAiDefaultsSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -38,7 +38,7 @@ async function openEditAgentDialog( page: import("@playwright/test").Page, agentName: string, ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); const agentButton = page.getByRole("button", { @@ -195,7 +195,7 @@ test("goose_per_agent_advanced_max_tokens_shows_inherited_global_placeholder", a ).toBeDisabled({ timeout: 5_000 }); // Step 3: navigate back and open the per-agent edit dialog for the Goose - // agent. We use the app's Back link rather than page.goto("/") to preserve + // agent. We use the app's Back link rather than bootstrapE2ePage(page, "/") to preserve // the in-memory mock state (page.goto causes a full reload that resets it). await page.getByRole("button", { name: "Back to app" }).click(); await page.getByTestId("open-agents-view").click(); diff --git a/desktop/tests/e2e/agent-provider-dropdowns.spec.ts b/desktop/tests/e2e/agent-provider-dropdowns.spec.ts index 8fa5df5bb0..dd39b16b9c 100644 --- a/desktop/tests/e2e/agent-provider-dropdowns.spec.ts +++ b/desktop/tests/e2e/agent-provider-dropdowns.spec.ts @@ -16,7 +16,7 @@ * Previously, the seeding effect bailed in edit mode, leaving the runtime * empty and the model dropdown silently blank. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -29,7 +29,7 @@ const SHOTS = "test-results/screenshots-dialogs"; * direct `/settings` request. */ async function openAiDefaultsSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -156,7 +156,7 @@ test.describe("agent provider dropdown screenshots", () => { ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agents-library-personas")).toBeVisible({ timeout: 10_000, @@ -229,7 +229,7 @@ test.describe("agent provider dropdown screenshots", () => { ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page .getByRole("button", { name: "Open actions for Codex Definition" }) diff --git a/desktop/tests/e2e/agent-readiness-screenshots.spec.ts b/desktop/tests/e2e/agent-readiness-screenshots.spec.ts index 7d34c4902a..8ab877aef5 100644 --- a/desktop/tests/e2e/agent-readiness-screenshots.spec.ts +++ b/desktop/tests/e2e/agent-readiness-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -15,7 +15,7 @@ const EDIT_AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey; * the "New agent" menu item, then fill a placeholder name. */ async function openCreateDialog(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); await page.locator("#persona-display-name").fill("Test Agent"); @@ -85,7 +85,7 @@ async function openEditDialog( page: import("@playwright/test").Page, agentName: string, ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); const agentButton = page.getByRole("button", { diff --git a/desktop/tests/e2e/agent-snapshot-recipient.spec.ts b/desktop/tests/e2e/agent-snapshot-recipient.spec.ts index 34f39b8fb2..513e927c08 100644 --- a/desktop/tests/e2e/agent-snapshot-recipient.spec.ts +++ b/desktop/tests/e2e/agent-snapshot-recipient.spec.ts @@ -5,7 +5,7 @@ * when an .agent.json or .agent.png attachment is detected, and the full * Add agent → preview → confirm flow. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; type CommandLogEntry = { command: string; payload: unknown }; @@ -107,7 +107,7 @@ async function seedAndSendSnapshot( ? { snapshotFetchError: opts.snapshotFetchError } : {}), }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // JSON snapshots can still arrive from saved files or older clients, even // though new in-app shares always encode PNG. Seed one directly so this diff --git a/desktop/tests/e2e/agents.spec.ts b/desktop/tests/e2e/agents.spec.ts index 46b14274bd..99a871ac0d 100644 --- a/desktop/tests/e2e/agents.spec.ts +++ b/desktop/tests/e2e/agents.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { hexToBytes } from "@noble/hashes/utils.js"; import { finalizeEvent, getPublicKey } from "nostr-tools/pure"; @@ -61,7 +61,7 @@ async function gotoApp(page: import("@playwright/test").Page) { let lastError: unknown = null; for (const attempt of [0, 1]) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); try { diff --git a/desktop/tests/e2e/animated-avatar.spec.ts b/desktop/tests/e2e/animated-avatar.spec.ts index e44cc65f6f..906cd7ace5 100644 --- a/desktop/tests/e2e/animated-avatar.spec.ts +++ b/desktop/tests/e2e/animated-avatar.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { installFakeCamera } from "../helpers/fakeCamera"; @@ -24,7 +24,7 @@ async function openAnimatedTab( await installMockBridge(page, { uploadDescriptors: [UPLOADED_PNG_DESCRIPTOR], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); await page.getByRole("tab", { name: "Animated" }).click(); diff --git a/desktop/tests/e2e/badge.spec.ts b/desktop/tests/e2e/badge.spec.ts index 5f8f344f16..21ac00d98c 100644 --- a/desktop/tests/e2e/badge.spec.ts +++ b/desktop/tests/e2e/badge.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -91,7 +91,7 @@ test.beforeEach(async ({ page }) => { test("selected Inbox and Agents rows keep their highlight without bold text", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const inbox = page .getByTestId("sidebar-primary-menu") @@ -108,7 +108,7 @@ test("selected Inbox and Agents rows keep their highlight without bold text", as test("primary navigation rows share the same inactive emphasis", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const primaryMenu = page.getByTestId("sidebar-primary-menu"); @@ -140,7 +140,7 @@ test("primary navigation rows share the same inactive emphasis", async ({ }); test("hovering a channel keeps its text color", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const channel = page.getByTestId("channel-engineering"); const initialColor = await channel.evaluate( (element) => getComputedStyle(element).color, @@ -156,7 +156,7 @@ test("direct-message rows become prominent only when unread", async ({ await page.addInitScript(() => { window.localStorage.setItem("buzz-theme", "buzz-dark"); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const directMessage = page.getByTestId("channel-alice-tyler"); await directMessage.click(); @@ -182,7 +182,7 @@ test("direct-message rows become prominent only when unread", async ({ test("light mode reserves full opacity for unread text and avatars", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const directMessage = page.getByTestId("channel-alice-tyler"); await directMessage.click(); @@ -226,7 +226,7 @@ test("dark mode keeps selected labels regular and channel-level unread labels bo await page.addInitScript(() => { window.localStorage.setItem("buzz-theme", "buzz-dark"); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator("html")).toHaveClass(/dark/); const inbox = page @@ -283,7 +283,7 @@ test("offscreen top-level unread shows the primary sidebar arrow", async ({ page, }) => { await page.setViewportSize({ width: 1280, height: 360 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await waitForMockLiveSubscription(page, "random"); await page.getByTestId("channel-general").click(); @@ -332,7 +332,7 @@ test("offscreen top-level unread shows the primary sidebar arrow", async ({ test("offscreen unread DM shows the primary sidebar arrow", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await waitForMockLiveSubscription(page, "alice-tyler"); await page.getByTestId("channel-general").click(); @@ -363,7 +363,7 @@ test("offscreen unread DM shows the primary sidebar arrow", async ({ test("regular message bolds inactive channel without numeric badge", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "random"); @@ -405,7 +405,7 @@ test("regular message bolds inactive channel without numeric badge", async ({ test("top-level @mention bolds the channel without a row badge", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "random"); @@ -437,7 +437,7 @@ test("top-level @mention bolds the channel without a row badge", async ({ }); test("numeric badge increments for DM message", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "alice-tyler"); @@ -458,7 +458,7 @@ test("numeric badge increments for DM message", async ({ page }) => { test("interested thread reply shows the channel preview dot without incrementing Inbox", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "random"); @@ -499,7 +499,7 @@ test("interested thread reply shows the channel preview dot without incrementing test("broadcast reply bolds the channel without a thread dot", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "random"); @@ -533,7 +533,7 @@ test("broadcast reply bolds the channel without a thread dot", async ({ test("mark-as-read via context menu clears channel unread indicator", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "random"); @@ -573,7 +573,7 @@ test("mark-as-read via context menu clears channel unread indicator", async ({ }); test("mark-as-unread via context menu bolds the channel", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -595,7 +595,7 @@ test("mark-as-unread via context menu bolds the channel", async ({ page }) => { test("marking a message unread bolds its channel after leaving", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); await waitForMockLiveSubscription(page, "random"); @@ -645,7 +645,7 @@ test("marking a message unread bolds its channel after leaving", async ({ test("remote read-state rollback is ignored while local mark-unread still increments badge", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/boot-splash.spec.ts b/desktop/tests/e2e/boot-splash.spec.ts index 614c1c28c6..6601a2e0d6 100644 --- a/desktop/tests/e2e/boot-splash.spec.ts +++ b/desktop/tests/e2e/boot-splash.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; // Cold-boot splash hold: on a real boot the community resolves in well under @@ -23,7 +23,7 @@ test("boot splash overlay holds with a flapping bee, then dismisses", async ({ bootSplashHoldMs: 1_500, }; }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const overlay = page.getByTestId("boot-splash-overlay"); await expect(overlay).toBeVisible(); @@ -50,7 +50,7 @@ test("boot splash overlay is skipped when the hold is zero (e2e default)", async page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await expect(page.getByTestId("boot-splash-overlay")).toHaveCount(0); diff --git a/desktop/tests/e2e/bootstrap.spec.ts b/desktop/tests/e2e/bootstrap.spec.ts index b489bb2ffc..206d6595a3 100644 --- a/desktop/tests/e2e/bootstrap.spec.ts +++ b/desktop/tests/e2e/bootstrap.spec.ts @@ -1,20 +1,20 @@ -import { expect, test } from "../helpers/test"; -import { E2E_APP_ORIGIN, bootstrapE2ePage } from "../helpers/bootstrap"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; +import { E2E_APP_ORIGIN } from "../helpers/bootstrap"; -test("automatically starts each test at the app origin", async ({ page }) => { +test.beforeEach(async ({ page }) => { + await bootstrapE2ePage(page); +}); + +test("starts each test at the app origin after explicit bootstrap", async ({ + page, +}) => { expect(new URL(page.url()).origin).toBe(E2E_APP_ORIGIN); }); -test.describe("pre-navigation setup", () => { - test.use({ e2eBootstrap: false }); - - test("reports a failed initial navigation at its source", async ({ - page, - }) => { - await expect( - bootstrapE2ePage(page, { expectedOrigin: "http://127.0.0.1:1" }), - ).rejects.toThrow( - /E2E app navigation did not commit to http:\/\/127\.0\.0\.1:1 \(current URL: about:blank\).*Run pnpm test:e2e:smoke or run pnpm build:e2e and check the preview server/, - ); - }); +test("reports a failed initial navigation at its source", async ({ page }) => { + await expect( + bootstrapE2ePage(page, { expectedOrigin: "http://127.0.0.1:1" }), + ).rejects.toThrow( + /E2E app navigation did not commit to http:\/\/127\.0\.0\.1:1 \(current URL: http:\/\/127\.0\.0\.1:4173\/\).*Run pnpm test:e2e:smoke or run pnpm build:e2e and check the preview server/, + ); }); diff --git a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts index a48d519fa1..1d47ed2ee6 100644 --- a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts +++ b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -51,7 +51,7 @@ async function seedIconChannelSection(page: Page) { } async function openChannel(page: Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); @@ -465,7 +465,7 @@ test("custom section icon and name align with channel columns", async ({ async function openAppearance(page: Page, mode: "system" | "light" | "dark") { // Settings renders at the AppShell level; open it via the profile card // button, then select the Appearance section. - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await page.getByTestId("settings-nav-appearance").click(); @@ -1171,7 +1171,7 @@ test("appearance picker — dark tab (Buzz Dark)", async ({ page }) => { test("settings nav uses Buzz active pill + hover (light)", async ({ page }) => { await seedTheme(page, "buzz"); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); const sidebar = page.getByTestId("settings-sidebar"); @@ -1202,7 +1202,7 @@ test("settings nav uses Buzz active pill + hover (light)", async ({ page }) => { test("settings nav uses Buzz active pill + hover (dark)", async ({ page }) => { await seedTheme(page, "buzz-dark"); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); const sidebar = page.getByTestId("settings-sidebar"); @@ -1431,7 +1431,7 @@ test("settings content uses the same inset surface as the main app", async ({ }) => { await seedTheme(page, "buzz"); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); const searchBox = await page.getByTestId("open-search").boundingBox(); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); diff --git a/desktop/tests/e2e/channel-activity-popover.spec.ts b/desktop/tests/e2e/channel-activity-popover.spec.ts index 2f47b7c660..edb79a4312 100644 --- a/desktop/tests/e2e/channel-activity-popover.spec.ts +++ b/desktop/tests/e2e/channel-activity-popover.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -159,7 +159,7 @@ async function seedChannelActivity( includeAgent = true, }: { extraThreadCount?: number; includeAgent?: boolean } = {}, ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -496,7 +496,7 @@ test.describe("channel activity hover preview", () => { "older-inbox-thread-for-hover", "second-inbox-thread-for-hover", ]; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByRole("button", { name: "Inbox", exact: true }).click(); @@ -639,7 +639,7 @@ test.describe("channel activity hover preview", () => { test("reading a grouped Inbox thread preserves an unrelated manual unread", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -701,7 +701,7 @@ test.describe("channel activity hover preview", () => { test("preserves Inbox ownership while another top-level row remains unread", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByRole("button", { name: "Inbox", exact: true }).click(); @@ -753,7 +753,7 @@ test.describe("channel activity hover preview", () => { test("surfaces future replies after the user reacts to a thread root", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); diff --git a/desktop/tests/e2e/channel-add-screenshots.spec.ts b/desktop/tests/e2e/channel-add-screenshots.spec.ts index aa43fd3b2e..be92070f41 100644 --- a/desktop/tests/e2e/channel-add-screenshots.spec.ts +++ b/desktop/tests/e2e/channel-add-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { test } from "../helpers/test"; +import { test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, openChannelBrowser } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -10,7 +10,7 @@ test.beforeEach(async ({ page }) => { }); test("capture: add-channel default state", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelBrowser(page); await page.getByTestId("channel-browser-dialog").waitFor(); await waitForAnimations(page); @@ -18,7 +18,7 @@ test("capture: add-channel default state", async ({ page }) => { }); test("capture: create row on partial match", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelBrowser(page); await page.getByTestId("channel-browser-search").fill("desig"); await page.getByTestId("channel-browser-create-row").waitFor(); @@ -27,7 +27,7 @@ test("capture: create row on partial match", async ({ page }) => { }); test("capture: create row on no match", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelBrowser(page); await page.getByTestId("channel-browser-search").fill("release-notes"); await page.getByTestId("channel-browser-create-row").waitFor(); @@ -36,7 +36,7 @@ test("capture: create row on no match", async ({ page }) => { }); test("capture: prefilled create form", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelBrowser(page); await page.getByTestId("channel-browser-search").fill("release-notes"); await page.getByTestId("channel-browser-create-row").click(); diff --git a/desktop/tests/e2e/channel-browser.spec.ts b/desktop/tests/e2e/channel-browser.spec.ts index 6ae419523d..d8e5b70b47 100644 --- a/desktop/tests/e2e/channel-browser.spec.ts +++ b/desktop/tests/e2e/channel-browser.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { E2E_APP_ORIGIN } from "../helpers/bootstrap"; import { installMockBridge, openChannelBrowser } from "../helpers/bridge"; @@ -30,6 +30,14 @@ test.beforeEach(async ({ page }, testInfo) => { ? { createChannelErrors: ["Create failed"] } : undefined, ); + if ( + testInfo.title.includes("custom section") || + testInfo.title.includes("canceling section create") || + testInfo.title.includes("failed section create") + ) { + await seedCustomSection(page); + } + await bootstrapE2ePage(page); }); test("keyboard shortcut opens the channel browser dialog", async ({ page }) => { @@ -220,8 +228,6 @@ test("sidebar add-channel button creates without treating the click as a callbac test("custom section add button creates directly into that section", async ({ page, }) => { - await seedCustomSection(page); - const addButton = page.getByTestId( `section-actions-${CUSTOM_SECTION.id}-quick-create`, ); @@ -245,8 +251,6 @@ test("custom section add button creates directly into that section", async ({ test("canceling section create does not affect the next global create", async ({ page, }) => { - await seedCustomSection(page); - await page .getByTestId(`section-actions-${CUSTOM_SECTION.id}-quick-create`) .click(); @@ -273,8 +277,6 @@ test("canceling section create does not affect the next global create", async ({ test("failed section create retry still assigns to the section", async ({ page, }) => { - await seedCustomSection(page); - await page .getByTestId(`section-actions-${CUSTOM_SECTION.id}-quick-create`) .click(); diff --git a/desktop/tests/e2e/channel-composer-overflow.spec.ts b/desktop/tests/e2e/channel-composer-overflow.spec.ts index b366c31398..3e98bc9fe1 100644 --- a/desktop/tests/e2e/channel-composer-overflow.spec.ts +++ b/desktop/tests/e2e/channel-composer-overflow.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -129,7 +129,7 @@ test.describe("composer overlays mask scrolled content", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL}`).click(); await expect(page.getByTestId("message-timeline")).toBeVisible(); await waitForMockLiveSubscription(page, CHANNEL); @@ -168,7 +168,7 @@ test.describe("composer overlays mask scrolled content", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL}`).click(); await waitForMockLiveSubscription(page, CHANNEL); await waitForMockLiveSubscription(page, CHANNEL, TYPING_KIND); @@ -203,7 +203,7 @@ test.describe("composer overlays mask scrolled content", () => { test("reduced motion removes dock geometry transitions", async ({ page }) => { await page.emulateMedia({ reducedMotion: "reduce" }); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL}`).click(); const dock = page @@ -216,7 +216,7 @@ test.describe("composer overlays mask scrolled content", () => { test("bottom-pinned messages clear a growing composer", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL}`).click(); await expect(page.getByTestId("message-timeline")).toBeVisible(); await waitForMockLiveSubscription(page, CHANNEL); @@ -281,7 +281,7 @@ test.describe("composer overlays mask scrolled content", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL}`).click(); await expect(page.getByTestId("message-timeline")).toBeVisible(); await waitForMockLiveSubscription(page, CHANNEL); diff --git a/desktop/tests/e2e/channel-controls.spec.ts b/desktop/tests/e2e/channel-controls.spec.ts index e1b1d93187..767e138c14 100644 --- a/desktop/tests/e2e/channel-controls.spec.ts +++ b/desktop/tests/e2e/channel-controls.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -6,7 +6,7 @@ import { installMockBridge } from "../helpers/bridge"; // visibility + ephemeral controls are editable. Visibility is a deferred // draft: selecting a value only updates local state; it persists on Save. async function openManagementSheet(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("channel-management-trigger").click(); diff --git a/desktop/tests/e2e/channel-dense-second-reach.spec.ts b/desktop/tests/e2e/channel-dense-second-reach.spec.ts index 8067080a33..37b487edc6 100644 --- a/desktop/tests/e2e/channel-dense-second-reach.spec.ts +++ b/desktop/tests/e2e/channel-dense-second-reach.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -30,7 +30,7 @@ test("dense single second beyond one window page is fully reachable via composit }, testInfo) => { testInfo.setTimeout(90_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/channel-mute.spec.ts b/desktop/tests/e2e/channel-mute.spec.ts index 76b9edcf7a..25f0116107 100644 --- a/desktop/tests/e2e/channel-mute.spec.ts +++ b/desktop/tests/e2e/channel-mute.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -51,7 +51,7 @@ async function waitForMockLiveSubscription( test.describe("channel muting", () => { test("01 — context menu shows Mute channel", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -75,7 +75,7 @@ test.describe("channel muting", () => { await seedMuteState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -95,7 +95,7 @@ test.describe("channel muting", () => { await seedMuteState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator("html")).toHaveClass(/dark/); await page.getByTestId("channel-random").click(); @@ -117,7 +117,7 @@ test.describe("channel muting", () => { await seedMuteState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-engineering").click(); await expect(page.getByTestId("chat-title")).toHaveText("engineering"); await waitForMockLiveSubscription(page, "engineering"); @@ -164,7 +164,7 @@ test.describe("channel muting", () => { await seedMuteState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -185,7 +185,7 @@ test.describe("channel muting", () => { await seedMuteState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-engineering").click(); await expect(page.getByTestId("chat-title")).toHaveText("engineering"); diff --git a/desktop/tests/e2e/channel-shared-header-backdrop.spec.ts b/desktop/tests/e2e/channel-shared-header-backdrop.spec.ts index a6d743f238..2103bd39c8 100644 --- a/desktop/tests/e2e/channel-shared-header-backdrop.spec.ts +++ b/desktop/tests/e2e/channel-shared-header-backdrop.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -45,7 +45,7 @@ test.describe("channel shared header backdrop", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`channel-${CHANNEL_NAME}`).click(); await expect(page.getByTestId("chat-title")).toHaveText(CHANNEL_NAME); await waitForMockLiveSubscription(page, CHANNEL_NAME); diff --git a/desktop/tests/e2e/channel-sort.spec.ts b/desktop/tests/e2e/channel-sort.spec.ts index c185f36806..f51d04b5dd 100644 --- a/desktop/tests/e2e/channel-sort.spec.ts +++ b/desktop/tests/e2e/channel-sort.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Page } from "@playwright/test"; import { waitForAnimations } from "../helpers/animations"; @@ -23,7 +23,7 @@ function seedSortState(page: Page, groups: Record) { } async function openApp(page: Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); } diff --git a/desktop/tests/e2e/channel-star.spec.ts b/desktop/tests/e2e/channel-star.spec.ts index 96e4f1851a..c009c9695e 100644 --- a/desktop/tests/e2e/channel-star.spec.ts +++ b/desktop/tests/e2e/channel-star.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -28,7 +28,7 @@ function seedStarState( test.describe("channel starring", () => { test("01 — context menu shows Star channel", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -49,7 +49,7 @@ test.describe("channel starring", () => { await seedStarState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -64,7 +64,7 @@ test.describe("channel starring", () => { await seedStarState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -90,7 +90,7 @@ test.describe("channel starring", () => { await seedStarState(page, ENGINEERING_CHANNEL_ID); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/channel-window-mock-paging.spec.ts b/desktop/tests/e2e/channel-window-mock-paging.spec.ts index 484fd25410..572ea71869 100644 --- a/desktop/tests/e2e/channel-window-mock-paging.spec.ts +++ b/desktop/tests/e2e/channel-window-mock-paging.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { parseChannelWindowResponse } from "@/features/messages/lib/channelWindowResponse"; import type { RelayEvent } from "@/shared/api/types"; @@ -42,7 +42,7 @@ test("mock-mode channel window pages parse with no dup or loss across page-1/pag page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -103,7 +103,7 @@ test("mock-mode channel window includes summaries and the two-hop aux closure", page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && diff --git a/desktop/tests/e2e/channels.spec.ts b/desktop/tests/e2e/channels.spec.ts index 0772317501..d17354e889 100644 --- a/desktop/tests/e2e/channels.spec.ts +++ b/desktop/tests/e2e/channels.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { KIND_HUDDLE_ENDED, @@ -499,7 +499,7 @@ test.beforeEach(async ({ page }, testInfo) => { }); test("sidebar shows all channel types", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await expect(page.getByTestId("sidebar-agents-count")).toHaveCount(0); @@ -545,7 +545,7 @@ test("shows cached profile labels while relay profiles revalidate", { { alicePubkey: TEST_IDENTITIES.alice.pubkey }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const aliceMessage = page @@ -560,7 +560,7 @@ test("shows cached profile labels while relay profiles revalidate", { test("shows presence in sidebar, DM header, and member list", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("sidebar-profile-card")).toBeVisible(); await expect(page.getByTestId("self-presence-badge")).toHaveAttribute( @@ -585,7 +585,7 @@ test("shows presence in sidebar, DM header, and member list", async ({ }); test("start a new direct message from the sidebar", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); @@ -640,7 +640,7 @@ test("start a new direct message from the sidebar", async ({ page }) => { test("keeps typing focus while arrow keys traverse and select DM recipients", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); const search = page.getByTestId("new-dm-search"); @@ -697,7 +697,7 @@ test("keeps typing focus while arrow keys traverse and select DM recipients", as test("sends the first message from the new direct message composer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -718,7 +718,7 @@ test("creates the DM before preparing a persona mention", async ({ page }) => { activePersonaIds: ["builtin:fizz"], createManagedAgentDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -817,7 +817,7 @@ test("routes an agent mention from an existing DM to the expanded conversation", activePersonaIds: ["builtin:fizz"], createManagedAgentDelayMs: 100, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const sourceDm = page.getByTestId("channel-alice-tyler"); const sourceDmId = await sourceDm.getAttribute("data-channel-id"); @@ -889,7 +889,7 @@ test("routes a managed relay-agent mention from an existing DM to the expanded c }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const sourceDm = page.getByTestId("channel-alice-tyler"); const sourceDmId = await sourceDm.getAttribute("data-channel-id"); @@ -941,7 +941,7 @@ test("does not reroute an expanded DM after the user navigates away", async ({ activePersonaIds: ["builtin:fizz"], sendMessageDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); @@ -973,7 +973,7 @@ test("does not reroute an expanded DM after the channel pane unmounts", async ({ activePersonaIds: ["builtin:fizz"], openDmDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); @@ -1009,7 +1009,7 @@ test("drops an expanded DM after the first message fails", async ({ page }) => { createManagedAgentDelayMs: 100, sendMessageErrors: [sendError], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -1091,7 +1091,7 @@ test("drops an expanded DM after agent startup fails", async ({ page }) => { activePersonaIds: ["builtin:fizz"], startManagedAgentErrors: [startError], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -1152,7 +1152,7 @@ test("drops an expanded DM after agent startup fails", async ({ page }) => { }); test("closes direct message results while opening", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const testWindow = window as Window & { __BUZZ_E2E__?: { mock?: { openDmDelayMs?: number } }; @@ -1185,7 +1185,7 @@ test("does not reopen a direct message after leaving the composer", async ({ page, }) => { await installMockBridge(page, { openDmDelayMs: 1_000 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -1213,7 +1213,7 @@ test("does not reopen a direct message after leaving the composer", async ({ test("opens a sent direct message without waiting for a channel-list refresh", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); @@ -1256,7 +1256,7 @@ test("opens a sent direct message without waiting for a channel-list refresh", a test("shows capped participant stack in group direct message header", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); @@ -1342,7 +1342,7 @@ test("shows capped participant stack in group direct message header", async ({ test("create stream with name and description", async ({ page }) => { const channelName = `my-new-stream-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page @@ -1383,7 +1383,7 @@ test("channel name values stay primary after their inputs blur", async ({ } } - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); const createDialog = page.getByTestId("create-channel-dialog"); await expectPrimaryValueAfterBlur( @@ -1441,7 +1441,7 @@ test("create channel template selector matches the lifecycle controls", async ({ ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); const templateControl = page.getByTestId("create-channel-template"); @@ -1474,7 +1474,7 @@ test("create channel exposes templates when the library is empty", async ({ page, }) => { await installMockBridge(page, { channelTemplates: [] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); const typeContainer = page.getByTestId( @@ -1521,7 +1521,7 @@ test("create ephemeral stream shows sidebar and header affordances", async ({ }) => { const channelName = `ephemeral-stream-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page @@ -1588,7 +1588,7 @@ test("ephemeral countdown refreshes when switching channels after a clock jump", const shiftedTime = new Date("2026-04-15T02:00:00.000Z"); await page.clock.setFixedTime(initialTime); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); for (const channelName of [firstChannelName, secondChannelName]) { await openCreateChannelDialog(page); @@ -1619,7 +1619,7 @@ test("archived channels stay out of all sidebar sections", async ({ page }) => { const archivedStreamName = `archived-stream-${Date.now()}`; const archivedForumName = `archived-forum-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction(() => { return Boolean( ( @@ -1692,7 +1692,7 @@ test("archived channels stay out of all sidebar sections", async ({ page }) => { test("create stream with special characters", async ({ page }) => { const channelName = `dev ops-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page @@ -1705,7 +1705,7 @@ test("create stream with special characters", async ({ page }) => { }); test("switch between streams", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1718,7 +1718,7 @@ test("switch between streams", async ({ page }) => { }); test("switch between channel types", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Stream await page.getByTestId("channel-general").click(); @@ -1734,7 +1734,7 @@ test("switch between channel types", async ({ page }) => { }); test("empty channel shows intro actions", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -1781,7 +1781,7 @@ test("short channel with messages shows intro actions on open", async ({ const channelName = `short-intro-${Date.now()}`; const message = "Only message in a short channel"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page.getByTestId("create-channel-submit").click(); @@ -1817,7 +1817,7 @@ test("scrollable channel with recent messages hides intro actions until top", as (_, index) => `Scrollable channel message ${index + 1}`, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page.getByTestId("create-channel-submit").click(); @@ -1858,7 +1858,7 @@ test("scrollable channel with recent messages hides intro actions until top", as }); test("channel with messages shows content", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1878,7 +1878,7 @@ test("channel with messages shows content", async ({ page }) => { test("channel date divider keeps the date sticky while the separator rule scrolls", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-engineering").click(); await expect(page.getByTestId("chat-title")).toHaveText("engineering"); @@ -2035,7 +2035,7 @@ test("channel date divider keeps the date sticky while the separator rule scroll test("shows and clears activity indicators for active channel agents", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -2121,7 +2121,7 @@ test("members sidebar exposes view-activity for a viewer-owned relay agent", asy }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "agents"); @@ -2140,7 +2140,7 @@ test("members sidebar exposes view-activity for a viewer-owned relay agent", asy test("profile renders live activity for a viewer-owned relay agent", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "agents"); await page @@ -2216,7 +2216,7 @@ test("profile renders live activity for a viewer-owned relay agent", async ({ test("profile activity carousel switches channels via progress dots", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "agents"); await page @@ -2290,7 +2290,7 @@ test("profile activity carousel switches channels via progress dots", async ({ test("typing indicator shows avatars and maintains stable name order", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -2356,7 +2356,7 @@ test("typing indicator shows avatars and maintains stable name order", async ({ test("sidebar shows unread indicator for newly active channels", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-unread-random")).toHaveCount(0); await waitForMockLiveSubscription(page, "random"); @@ -2390,7 +2390,7 @@ test("sidebar shows unread indicator for newly active channels", async ({ }); test("sidebar shows unread indicator for new forum posts", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-unread-watercooler")).toHaveCount(0); await waitForMockLiveSubscription(page, "watercooler"); @@ -2420,7 +2420,7 @@ test("sidebar shows unread indicator for new forum posts", async ({ page }) => { }); test("sidebar clears unread indicator after opening a DM", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-unread-alice-tyler")).toHaveCount(0); await waitForMockLiveSubscription(page, "alice-tyler"); @@ -2451,7 +2451,7 @@ test("sidebar clears unread indicator after opening a DM", async ({ page }) => { }); test("sidebar persists after channel switch", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); @@ -2473,7 +2473,7 @@ test("manage channel updates details", async ({ page }) => { const newName = `release-hub-${stamp}`; const newDescription = `Release coordination ${stamp}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "general"); await openChannelEditDialog(page); const editDialog = page.getByRole("dialog", { @@ -2534,7 +2534,7 @@ test("manage channel shows member avatars and owner-only row controls", async ({ page, }) => { await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "general"); await expect( @@ -2703,7 +2703,7 @@ test("manage channel shows member avatars and owner-only row controls", async ({ test("direct message settings omit the channel name and empty actions card", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "alice-tyler"); await expect(page.getByTestId("channel-management-hero")).toBeVisible(); @@ -2723,7 +2723,7 @@ test("direct message settings omit the channel name and empty actions card", asy test("channel settings only prompt editors to add an empty description", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_MUTATE_CHANNEL__ === "function" && @@ -2772,7 +2772,7 @@ test("channel settings only prompt editors to add an empty description", async ( test("manage channel updates visibility and ephemeral lifecycle independently", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "general"); await openChannelEditDialog(page); @@ -2900,7 +2900,7 @@ test("manage channel updates visibility and ephemeral lifecycle independently", test("manage channel places canvas between channel info and actions", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "general"); const sheet = page.getByTestId("channel-management-sheet"); @@ -2951,7 +2951,7 @@ async function seedHomeInboxMention( { navigate = true }: { navigate?: boolean } = {}, ) { if (navigate) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); } await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction( @@ -3004,7 +3004,7 @@ async function seedHomeInboxMention( } test("Inbox All excludes generic channel traffic", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction(() => { const win = window as MockFeedWindow; return typeof win.__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__ === "function"; @@ -3066,7 +3066,7 @@ test("Inbox type labels keep the same height with and without a channel chip", a const mentionId = "inbox-type-label-mention"; const dmChannelId = "f48efb06-0c93-5025-aac9-2e646bb6bfa8"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction(() => { const win = window as MockFeedWindow; @@ -3191,7 +3191,7 @@ test("Inbox All never lists drafts and unread-only hides reminders", async ({ draftStoreKey: `buzz-drafts.v2:ws://localhost:3000:${MOCK_IDENTITY_PUBKEY}`, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction(() => { const win = window as MockFeedWindow; return typeof win.__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__ === "function"; @@ -3349,7 +3349,7 @@ test("Inbox merges a due reminder into its represented conversation", async ({ test("Inbox All keeps its filter when opening a due reminder", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox")).toBeVisible(); const reminderId = "inbox-stable-reminder"; @@ -3399,7 +3399,7 @@ test("Inbox All keeps its filter when opening a due reminder", async ({ }); test("Inbox reminder rows and detail identify DM context", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox")).toBeVisible(); const reminderId = "inbox-dm-reminder"; @@ -3564,7 +3564,7 @@ test("Inbox filter changes preserve valid detail and directly select a replaceme test("Inbox keeps the unread boundary for replies from multiple agents", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction(() => { const win = window as MockFeedWindow; @@ -3657,7 +3657,7 @@ test("home inbox groups consecutive DMs and opens the full conversation", async const dmChannelId = "f48efb06-0c93-5025-aac9-2e646bb6bfa8"; const dmIds = ["inbox-dm-first", "inbox-dm-second", "inbox-dm-third"]; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction(() => { const win = window as MockFeedWindow; @@ -3784,7 +3784,7 @@ test("home inbox manage affordance opens management without leaving home", async test("home channel settings keeps agent lifecycle actions scoped to the active community", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentPubkey = await addGenericAgent( page, "general", @@ -3834,7 +3834,7 @@ test("home channel settings keeps agent lifecycle actions scoped to the active c }); test("members sidebar virtualizes large channel rosters", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const channelId = await page .getByTestId("channel-random") .getAttribute("data-channel-id"); @@ -3878,7 +3878,7 @@ test("members sidebar can invite relay-authorized agents", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await page.getByTestId("channel-management-search-users").fill("quinn"); @@ -3901,7 +3901,7 @@ test("members sidebar hides relay agents that are not authorized", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await page.getByTestId("channel-management-search-users").fill("quinn"); @@ -3923,7 +3923,7 @@ test("members sidebar can invite and remove managed agents", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); const initialMemberCount = await readMembersTriggerCount(page); await expect(page.getByTestId("channel-management-add-pubkeys")).toHaveCount( @@ -3981,7 +3981,7 @@ test("members sidebar pages add-member search beyond the first 50 people", async })); await installMockBridge(page, { searchProfiles }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await page.getByTestId("channel-management-search-users").fill("Alex"); @@ -4018,7 +4018,7 @@ test("shows loading skeletons without stale recipient results", async ({ relayAgents: [], userSearchDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); const search = page.getByTestId("new-dm-search"); @@ -4050,7 +4050,7 @@ test("new DM picker pages people search beyond the first 50 results", async ({ })); await installMockBridge(page, { searchProfiles }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); await page.getByTestId("new-dm-search").fill("Alex"); @@ -4087,7 +4087,7 @@ test("member people search starts at two characters", async ({ page }) => { searchProfiles: [{ pubkey: jmPubkey, displayName: "jm" }], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await page.getByTestId("channel-management-search-users").fill("j"); await expect( @@ -4119,7 +4119,7 @@ test("member people search starts at two characters", async ({ page }) => { }); test("members modal does not show direct pubkey entry", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await expect(page.getByTestId("channel-management-add-pubkeys")).toHaveCount( @@ -4134,7 +4134,7 @@ test("members modal does not show direct pubkey entry", async ({ page }) => { }); test("channel header omits the add agent action", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -4149,7 +4149,7 @@ test("channel header omits the add agent action", async ({ page }) => { test("huddle rollback end event clears the active header action", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); await waitForMockLiveSubscription(page, "random", KIND_HUDDLE_STARTED); @@ -4199,7 +4199,7 @@ test("huddle rollback end event clears the active header action", async ({ }); test("channel header actions show tooltips", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); @@ -4253,7 +4253,7 @@ test("members sidebar retains distinct same-persona managed agents", async ({ ], userSearchDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); await page.getByTestId("channel-management-search-users").fill("pi"); @@ -4279,7 +4279,7 @@ test("private-channel members can add people and managed agents without admin", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // secret-projects is a private (non-DM) channel where the current user is a // plain member. Active members may add ordinary members and bots; only // elevated-role grants and role changes require owner/admin authority. @@ -4311,7 +4311,7 @@ test("open-channel members can add people and managed agents without admin", asy }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // random is open and the current user is a plain member there, so the // owner/admin requirement must not leak outside private channels. await openMembersSidebar(page, "random"); @@ -4344,7 +4344,7 @@ test("removing a channel-scoped agent preserves the managed agent record", async }) => { const agentName = `cleanup-agent-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentPubkey = await addGenericAgent(page, "general", agentName); await page.getByTestId("channel-general").click(); @@ -4367,7 +4367,7 @@ test("members sidebar can stop and start a managed bot in this community", async }) => { const agentName = `sidebar-agent-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentPubkey = await addGenericAgent(page, "general", agentName); const baselineCommands = await readCommandLog(page); const baselineLegacyStartCount = commandCount( @@ -4443,7 +4443,7 @@ test("stopping a managed bot in one community leaves its other communities runni { pubkey: agentPubkey, relayUrl: otherRelayUrl }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openMembersSidebar(page, "general"); const agentStatus = page.getByTestId( @@ -4496,7 +4496,7 @@ test("members sidebar omits bulk controls for managed bots", async ({ const firstAgentName = `sidebar-remove-a-${Date.now()}`; const secondAgentName = `sidebar-remove-b-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const firstAgentPubkey = await addGenericAgent( page, "general", @@ -4543,7 +4543,7 @@ test("removing a multi-channel managed bot preserves its record after removal fr const agentName = `multi-channel-agent-${Date.now()}`; const secondChannelName = `multi-home-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentPubkey = await addGenericAgent(page, "general", agentName); await openCreateChannelDialog(page); @@ -4618,7 +4618,7 @@ test("bulk remove stays hidden when row-level remove is not allowed", async ({ const alicePubkey = "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Join the "design" channel (unjoined by default) via the channel browser. // The user becomes a regular member — not admin/owner. @@ -4648,7 +4648,7 @@ test("bulk remove stays hidden when row-level remove is not allowed", async ({ }); test("open channel management supports join and leave", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Navigate to "design" (an unjoined channel) via the channel browser await openChannelBrowser(page); @@ -4690,7 +4690,7 @@ test("open channel management supports join and leave", async ({ page }) => { }); test("manage channel can archive and unarchive a stream", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelManagement(page, "general"); await page.getByTestId("channel-management-archive").click(); @@ -4729,7 +4729,7 @@ test("manage channel can archive and unarchive a stream", async ({ page }) => { test("manage channel can delete an owned stream", async ({ page }) => { const channelName = `delete-me-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page.getByTestId("create-channel-submit").click(); @@ -4750,7 +4750,7 @@ test("manage channel can delete an owned stream", async ({ page }) => { test("canceling channel deletion keeps the owned stream", async ({ page }) => { const channelName = `keep-me-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page.getByTestId("create-channel-submit").click(); diff --git a/desktop/tests/e2e/cold-switch-longtask.perf.ts b/desktop/tests/e2e/cold-switch-longtask.perf.ts index 37de757714..fc15500d72 100644 --- a/desktop/tests/e2e/cold-switch-longtask.perf.ts +++ b/desktop/tests/e2e/cold-switch-longtask.perf.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -57,7 +57,7 @@ test("MEASURE: cold-switch longtask cost into deep-history at the 300 ceiling", }) => { test.setTimeout(120_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/community-rail.spec.ts b/desktop/tests/e2e/community-rail.spec.ts index e2e9db9e7c..a67e9d7943 100644 --- a/desktop/tests/e2e/community-rail.spec.ts +++ b/desktop/tests/e2e/community-rail.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { FEATURE_OVERRIDES_STORAGE_KEY } from "../helpers/features"; @@ -72,7 +72,7 @@ test.describe("community rail", () => { JSON.stringify({ workspaceRail: false }), ); }, FEATURE_OVERRIDES_STORAGE_KEY); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rail = page.getByTestId("community-rail"); await expect(rail).toBeVisible(); @@ -165,7 +165,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const communityButton = page.getByTestId( `community-rail-button-${COMMUNITY_A.id}`, @@ -205,7 +205,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page .getByTestId(`community-rail-button-${COMMUNITY_A.id}`) @@ -252,7 +252,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page .getByTestId(`community-rail-button-${COMMUNITY_A.id}`) @@ -281,7 +281,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("sidebar-profile-avatar-button").click(); const communityTrigger = page.getByTestId("community-switcher"); @@ -420,7 +420,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("sidebar-profile-avatar-button").click(); await page.getByTestId("community-switcher").click(); @@ -461,7 +461,7 @@ test.describe("community rail", () => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`).click(); @@ -503,7 +503,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -563,7 +563,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -622,7 +622,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -669,7 +669,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page).toHaveURL(/#\/channels\//); @@ -701,7 +701,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); const rememberedChannelId = await page.evaluate( ({ communityId, sourceSnapshotKey, targetSnapshotKey }) => { @@ -771,7 +771,7 @@ test.describe("community rail", () => { ); }, COMMUNITY_B.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const sourceSnapshotKey = snapshotKey(COMMUNITY_A.relayUrl); const targetSnapshotKey = snapshotKey(COMMUNITY_B.relayUrl); await expect @@ -824,7 +824,7 @@ test.describe("community rail", () => { }), ); }, COMMUNITY_B.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); const sourceSnapshotKey = snapshotKey(COMMUNITY_A.relayUrl); await expect @@ -1059,7 +1059,7 @@ test.describe("community rail", () => { ); }, COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page).not.toHaveURL(/#\/channels\//); }); @@ -1069,7 +1069,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`).click(); await page.getByTestId("channel-random").click(); @@ -1104,7 +1104,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Cold boot still uses the full splash. await expect(page.getByTestId("app-loading-gate")).toBeVisible(); @@ -1141,7 +1141,7 @@ test.describe("community rail", () => { skipCommunitySeed: true, }); await seedCommunities(page, [COMMUNITY_A], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect .poll(() => @@ -1209,7 +1209,7 @@ test.describe("community rail", () => { { skipCommunitySeed: true }, ); await seedCommunities(page, [COMMUNITY_A], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("sidebar-profile-avatar-button").click(); await page.getByTestId("community-switcher").click(); @@ -1263,7 +1263,7 @@ test.describe("community rail", () => { }, THEME_STORAGE_KEY); await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // The channel sidebar still renders; the rail is omitted (a rail of one // adds nothing). @@ -1295,7 +1295,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rail = page.getByTestId("community-rail"); await expect(rail).toBeVisible(); @@ -1333,7 +1333,7 @@ test.describe("community rail", () => { }); await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // The first community button must start below the traffic-light band // (native controls sit around y<=31 with trafficLightPosition y:24). @@ -1409,7 +1409,7 @@ test.describe("community rail", () => { }, { list: [COMMUNITY_A, COMMUNITY_B], active: COMMUNITY_A.id }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const buttonA = page.getByTestId(`community-rail-button-${COMMUNITY_A.id}`); const buttonB = page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`); @@ -1481,7 +1481,7 @@ test.describe("community rail", () => { }) => { await installMockBridge(page, undefined, { skipCommunitySeed: true }); await seedCommunities(page, [COMMUNITY_A, COMMUNITY_B], COMMUNITY_A.id); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const buttonA = page.getByTestId(`community-rail-button-${COMMUNITY_A.id}`); const buttonB = page.getByTestId(`community-rail-button-${COMMUNITY_B.id}`); diff --git a/desktop/tests/e2e/composer-image-draw.spec.ts b/desktop/tests/e2e/composer-image-draw.spec.ts index 3e0a692aa2..1ff27a8014 100644 --- a/desktop/tests/e2e/composer-image-draw.spec.ts +++ b/desktop/tests/e2e/composer-image-draw.spec.ts @@ -1,4 +1,4 @@ -import { expect, type Page, test } from "../helpers/test"; +import { expect, type Page, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -70,7 +70,7 @@ test.beforeEach(async ({ page }) => { }); test("image annotation overlay and editor controls", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByRole("button", { name: "Attach file" }).click(); @@ -97,7 +97,7 @@ test("image annotation overlay and editor controls", async ({ page }) => { test("draw on an uploaded image, save replaces it, revert restores in place", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -178,7 +178,7 @@ test("draw on an uploaded image, save replaces it, revert restores in place", as }); test("spoiler marking survives drawing on the attachment", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/composer-link-shortcut.spec.ts b/desktop/tests/e2e/composer-link-shortcut.spec.ts index 1479bfe90b..ab432beae3 100644 --- a/desktop/tests/e2e/composer-link-shortcut.spec.ts +++ b/desktop/tests/e2e/composer-link-shortcut.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Page } from "@playwright/test"; import { installMockBridge } from "../helpers/bridge"; @@ -13,7 +13,7 @@ import { installMockBridge } from "../helpers/bridge"; // - With focus outside the composer → quick search, unchanged. async function openGeneral(page: Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); } diff --git a/desktop/tests/e2e/composer-selection-formatting.spec.ts b/desktop/tests/e2e/composer-selection-formatting.spec.ts index 38574be9e3..9078ba6118 100644 --- a/desktop/tests/e2e/composer-selection-formatting.spec.ts +++ b/desktop/tests/e2e/composer-selection-formatting.spec.ts @@ -1,9 +1,15 @@ -import { expect, test, type Locator, type Page } from "../helpers/test"; +import { + expect, + test, + type Locator, + type Page, + bootstrapE2ePage, +} from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; async function openGeneral(page: Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); } diff --git a/desktop/tests/e2e/composer-tooltip-dismiss.spec.ts b/desktop/tests/e2e/composer-tooltip-dismiss.spec.ts index 9d82c74f6a..59468e1484 100644 --- a/desktop/tests/e2e/composer-tooltip-dismiss.spec.ts +++ b/desktop/tests/e2e/composer-tooltip-dismiss.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; /** @@ -39,7 +39,7 @@ async function expectTooltipDismissesOnLeave( test("composer toolbar tooltip dismisses when cursor leaves the trigger", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -73,7 +73,7 @@ test("adjacent composer tooltips each require a fresh dwell", async ({ test("formatting sub-toolbar tooltip dismisses when cursor leaves the trigger", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -93,7 +93,7 @@ test("formatting sub-toolbar tooltip dismisses when cursor leaves the trigger", test("emoji picker tooltip dismisses when cursor leaves the trigger", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/config-bridge-screenshots.spec.ts b/desktop/tests/e2e/config-bridge-screenshots.spec.ts index 02dd5df38d..642d543e10 100644 --- a/desktop/tests/e2e/config-bridge-screenshots.spec.ts +++ b/desktop/tests/e2e/config-bridge-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -113,7 +113,7 @@ async function openAgentProfileFromChannel( tab?: "Info" | "Runtime"; } = {}, ) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); await activatePersonas(page); @@ -326,7 +326,7 @@ test.describe("config bridge screenshots", () => { }, ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); diff --git a/desktop/tests/e2e/core-memory-screenshots.spec.ts b/desktop/tests/e2e/core-memory-screenshots.spec.ts index 6b85b11917..20a6ab092a 100644 --- a/desktop/tests/e2e/core-memory-screenshots.spec.ts +++ b/desktop/tests/e2e/core-memory-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -40,7 +40,7 @@ async function openObserverFeedPanel( page: import("@playwright/test").Page, agentPubkey: string, ) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForSeedHook(page); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); diff --git a/desktop/tests/e2e/custom-emoji-ui.spec.ts b/desktop/tests/e2e/custom-emoji-ui.spec.ts index bc3f862a54..ee97c841ed 100644 --- a/desktop/tests/e2e/custom-emoji-ui.spec.ts +++ b/desktop/tests/e2e/custom-emoji-ui.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -21,7 +21,7 @@ test.beforeEach(async ({ page }) => { }); test("composer renders a custom emoji inline", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -34,7 +34,7 @@ test("composer renders a custom emoji inline", async ({ page }) => { test("settings card splits My emoji from read-only Community emoji", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -60,7 +60,7 @@ test("settings card splits My emoji from read-only Community emoji", async ({ test("message list renders inline and emoji-only messages with Slack-style emoji sizing", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/custom-emoji.spec.ts b/desktop/tests/e2e/custom-emoji.spec.ts index 5bec9a68e6..ec0a100d46 100644 --- a/desktop/tests/e2e/custom-emoji.spec.ts +++ b/desktop/tests/e2e/custom-emoji.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import * as fs from "node:fs"; import * as path from "node:path"; @@ -44,7 +44,7 @@ async function waitForMockLiveSubscription( } async function openGeneral(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); diff --git a/desktop/tests/e2e/deep-link-invite.spec.ts b/desktop/tests/e2e/deep-link-invite.spec.ts index affea918f7..17ed424d42 100644 --- a/desktop/tests/e2e/deep-link-invite.spec.ts +++ b/desktop/tests/e2e/deep-link-invite.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { seedActiveIdentity } from "../helpers/onboarding"; @@ -55,7 +55,7 @@ test("join deep link is acknowledged without claiming before setup", async ({ { pendingCommunityDeepLinks: [PENDING_JOIN_LINK] }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const gate = page.getByTestId("pending-invite-gate"); await expect(gate).toBeVisible(); @@ -86,7 +86,7 @@ test("connect deep link shows a static acknowledgment during setup", async ({ { pendingCommunityDeepLinks: [PENDING_CONNECT_LINK] }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const gate = page.getByTestId("pending-invite-gate"); await expect(gate).toBeVisible(); @@ -124,7 +124,7 @@ test("add-community deep link starts onboarding when no community is configured" }, { skipCommunitySeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("community-onboarding-flow")).toBeVisible(); await expect( @@ -159,7 +159,7 @@ test("add-community deep link skips profile step when identity has an existing k { pendingCommunityDeepLinks: [PENDING_ADD_COMMUNITY_LINK] }, { skipCommunitySeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Onboarding flow must disappear — the skip cleared the transaction. await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0); @@ -176,7 +176,7 @@ test("add-community deep link opens one editable prefill and acknowledges the qu { pendingCommunityDeepLinks: [PENDING_ADD_COMMUNITY_LINK] }, { seedPreviewFeatures: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("heading", { name: "Join an existing community" }), @@ -222,7 +222,7 @@ test("queued add-community links open and acknowledge one at a time", async ({ }, { seedPreviewFeatures: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const communityInput = page.getByLabel("Community URL or invite link"); await expect(communityInput).toHaveValue(PENDING_ADD_COMMUNITY_LINK.relayUrl); @@ -300,7 +300,7 @@ test("deleted public starter channels do not strand community onboarding", async { ensureStarterChannelsErrors: [starterError] }, { relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Take me to Buzz" }).click(); @@ -355,7 +355,7 @@ test("required Welcome creation failure keeps community onboarding open", async { createChannelErrors: [welcomeError] }, { relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Take me to Buzz" }).click(); @@ -401,7 +401,7 @@ test("persisted deep-link invite hands off to Joining after machine onboarding", skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Machine onboarding is complete, so the transaction owns the screen. await expect(page.getByTestId("community-onboarding-flow")).toBeVisible(); diff --git a/desktop/tests/e2e/dm-double-notification.spec.ts b/desktop/tests/e2e/dm-double-notification.spec.ts index 4e5056f774..4b7896e7c8 100644 --- a/desktop/tests/e2e/dm-double-notification.spec.ts +++ b/desktop/tests/e2e/dm-double-notification.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { finalizeEvent } from "nostr-tools/pure"; import { hexToBytes } from "@noble/hashes/utils.js"; @@ -82,7 +82,7 @@ test("an incoming DM produces exactly one desktop notification", async ({ (response.request().postData() ?? "").includes('"#p"'), { timeout: 15_000 }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Wait until the DM channel is loaded — live subscriptions (channel + home // feed mention) are established once the channel list resolves. diff --git a/desktop/tests/e2e/dm-history-live-regression.spec.ts b/desktop/tests/e2e/dm-history-live-regression.spec.ts index fad0b0afcb..5171dcc2e9 100644 --- a/desktop/tests/e2e/dm-history-live-regression.spec.ts +++ b/desktop/tests/e2e/dm-history-live-regression.spec.ts @@ -1,4 +1,10 @@ -import { expect, test, type Page, type Route } from "../helpers/test"; +import { + expect, + test, + type Page, + type Route, + bootstrapE2ePage, +} from "../helpers/test"; import { hexToBytes } from "@noble/hashes/utils.js"; import { finalizeEvent, type VerifiedEvent } from "nostr-tools/pure"; @@ -132,7 +138,7 @@ test("existing DM history remains when the first live DM reaches a pageless wind const historyIds = history.map((event) => event.id); await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); await expect.poll(() => timelineIds(page)).toEqual(historyIds); diff --git a/desktop/tests/e2e/dm-new-message-screenshots.spec.ts b/desktop/tests/e2e/dm-new-message-screenshots.spec.ts index 93a9b561e5..af6501b0aa 100644 --- a/desktop/tests/e2e/dm-new-message-screenshots.spec.ts +++ b/desktop/tests/e2e/dm-new-message-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { @@ -15,7 +15,7 @@ test("captures the new-message loading skeleton", async ({ page }) => { relayAgents: [], userSearchDelayMs: 10_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); const search = page.getByTestId("new-dm-search"); @@ -32,7 +32,7 @@ test("captures the new-message loading skeleton", async ({ page }) => { test("captures selected recipients with the picker open", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); for (const identity of [TEST_IDENTITIES.charlie, TEST_IDENTITIES.bob]) { @@ -54,7 +54,7 @@ test("captures selected recipients with the picker open", async ({ page }) => { test("captures the enabled first-message composer", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await page.getByTestId("new-dm-search").fill("charlie"); diff --git a/desktop/tests/e2e/doctor-cta-screenshots.spec.ts b/desktop/tests/e2e/doctor-cta-screenshots.spec.ts index 85d872b98f..9c68a58493 100644 --- a/desktop/tests/e2e/doctor-cta-screenshots.spec.ts +++ b/desktop/tests/e2e/doctor-cta-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -139,7 +139,7 @@ test.describe("doctor CTA nudge card screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); const content = makeNudgeSentinel(AGENT_NAME, AGENT_PUBKEY, [ { @@ -182,7 +182,7 @@ test.describe("doctor CTA nudge card screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); const content = makeNudgeSentinel(AGENT_NAME, AGENT_PUBKEY, [ { @@ -225,7 +225,7 @@ test.describe("doctor CTA nudge card screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); const content = makeNudgeSentinel(AGENT_NAME, AGENT_PUBKEY, [ { @@ -273,7 +273,7 @@ test.describe("doctor CTA nudge card screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); const content = makeNudgeSentinel(AGENT_NAME, AGENT_PUBKEY, [ { diff --git a/desktop/tests/e2e/doctor-states.spec.ts b/desktop/tests/e2e/doctor-states.spec.ts index 29f03ba9fa..3b8359b1ec 100644 --- a/desktop/tests/e2e/doctor-states.spec.ts +++ b/desktop/tests/e2e/doctor-states.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -125,7 +125,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const runtimeList = page.getByTestId("doctor-runtime-list"); @@ -243,7 +243,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-claude"); @@ -286,7 +286,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-codex"); @@ -331,7 +331,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-claude"); @@ -379,7 +379,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); // Node-gated entries never get a Your-harnesses row (and thus never an @@ -481,7 +481,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-codex"); @@ -563,7 +563,7 @@ test.describe("Doctor panel state screenshots", () => { }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const installButton = page.getByTestId("doctor-runtime-install-codex"); @@ -630,7 +630,7 @@ test.describe("Doctor panel state screenshots", () => { }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-codex"); @@ -683,7 +683,7 @@ test.describe("Doctor panel state screenshots", () => { }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-claude"); @@ -716,7 +716,7 @@ test.describe("Doctor panel state screenshots", () => { }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); await expect(page.getByTestId("doctor-runtime-error-codex")).toContainText( @@ -750,7 +750,7 @@ test.describe("Doctor panel state screenshots", () => { connectAcpRuntimeError: "The browser could not be opened.", }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); await page.getByTestId("doctor-runtime-menu-codex").click(); @@ -786,7 +786,7 @@ test.describe("Doctor panel state screenshots", () => { connectAcpRuntimeResult: { launched: true }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); await page.getByTestId("doctor-runtime-menu-codex").click(); @@ -815,7 +815,7 @@ test.describe("Doctor panel state screenshots", () => { installAcpRuntimeDelayMs: 250, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); await expect(page.getByTestId("doctor-runtime-status-codex")).toHaveText( @@ -924,7 +924,7 @@ test.describe("Doctor panel state screenshots", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const claudeRow = page.getByTestId("doctor-runtime-claude"); @@ -1010,7 +1010,7 @@ test.describe("Doctor panel state screenshots", () => { }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); const row = page.getByTestId("doctor-runtime-codex"); diff --git a/desktop/tests/e2e/drafts-screenshots.spec.ts b/desktop/tests/e2e/drafts-screenshots.spec.ts index bdb5fb91a2..7386290c6b 100644 --- a/desktop/tests/e2e/drafts-screenshots.spec.ts +++ b/desktop/tests/e2e/drafts-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -107,7 +107,7 @@ async function seedDraftStore( /** Navigate to `/`, wait for inbox, then select the Drafts filter. */ async function openDraftsPanel(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("home-inbox")).toBeVisible({ timeout: 10_000 }); await page.getByTestId("inbox-filter-trigger").click(); @@ -354,7 +354,7 @@ test.describe("drafts screenshots", () => { await patchCommunityPubkey(page); await seedDraftStore(page, ACTIVE_DRAFTS); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("home-inbox")).toBeVisible({ timeout: 10_000, }); diff --git a/desktop/tests/e2e/edit-agent-run-on.spec.ts b/desktop/tests/e2e/edit-agent-run-on.spec.ts index c8072e4272..4862bf3957 100644 --- a/desktop/tests/e2e/edit-agent-run-on.spec.ts +++ b/desktop/tests/e2e/edit-agent-run-on.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -28,7 +28,7 @@ async function openEditDialog( page: import("@playwright/test").Page, agentName: string, ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page .getByRole("button", { name: `${agentName} agent profile` }) diff --git a/desktop/tests/e2e/edit-agent.spec.ts b/desktop/tests/e2e/edit-agent.spec.ts index 7ae830856d..448dd66acc 100644 --- a/desktop/tests/e2e/edit-agent.spec.ts +++ b/desktop/tests/e2e/edit-agent.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -42,7 +42,7 @@ const PERSONA_ID = "persona-edit-e2e"; * only mount path. */ async function openEditDialog(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); const agentButton = page.getByRole("button", { @@ -86,7 +86,7 @@ test.describe("agent definition dialog", () => { ownerOnlyAccessBuild: true, bakedBuildEnv: BAKED_DEFAULTS, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); @@ -381,7 +381,7 @@ test.describe("edit agent dialog", () => { ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); // Persona-linked agents render grouped under the persona's card name. diff --git a/desktop/tests/e2e/empty-edit-delete.spec.ts b/desktop/tests/e2e/empty-edit-delete.spec.ts index 0c5df3068e..5b0e3a67dc 100644 --- a/desktop/tests/e2e/empty-edit-delete.spec.ts +++ b/desktop/tests/e2e/empty-edit-delete.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -44,7 +44,7 @@ async function submitEmptyEdit( test.beforeEach(async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); }); diff --git a/desktop/tests/e2e/entity-link-recipient-cards.spec.ts b/desktop/tests/e2e/entity-link-recipient-cards.spec.ts index c0e2fca838..b6c5bc6efc 100644 --- a/desktop/tests/e2e/entity-link-recipient-cards.spec.ts +++ b/desktop/tests/e2e/entity-link-recipient-cards.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -51,7 +51,7 @@ test("agent-style message with bare buzz:// links renders entity cards without s ); await installMockBridge(page); await page.setViewportSize({ width: 900, height: 800 }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("channel-general").click(); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", @@ -134,7 +134,7 @@ test("desktop composer shows entity card and send is not blocked by missing snap }) => { await installMockBridge(page); await page.setViewportSize({ width: 900, height: 800 }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("channel-general").click(); const repoLink = `buzz://repo?owner=${ALICE_PUBKEY}&d=relay-tools`; @@ -215,7 +215,7 @@ test("reopening the same entity link reapplies its workspace state", async ({ }, ); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("open-projects-view")).toBeVisible(); const repoLink = `buzz://repo?owner=${DEFAULT_MOCK_PUBKEY}&d=buzz&tab=prs`; const prLink = `buzz://pr?id=${PR_ID}&owner=${DEFAULT_MOCK_PUBKEY}&d=buzz`; @@ -287,7 +287,7 @@ test("cold-start entity links drain after the React listener mounts", async ({ pendingEntityDeepLinks: [{ id: "cold-start-project", href }], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect( page.getByRole("tab", { name: "Pull Request", exact: true }), diff --git a/desktop/tests/e2e/file-attachment.spec.ts b/desktop/tests/e2e/file-attachment.spec.ts index 8d7344910c..f5aff3f684 100644 --- a/desktop/tests/e2e/file-attachment.spec.ts +++ b/desktop/tests/e2e/file-attachment.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Page } from "@playwright/test"; import { waitForAnimations } from "../helpers/animations"; @@ -91,7 +91,7 @@ async function uploadCommandCount(page: Page) { test("picker survives cancel, same-file retry, and multiple selection", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const attach = page.getByRole("button", { name: "Attach file" }); @@ -125,7 +125,7 @@ test("picker survives cancel, same-file retry, and multiple selection", async ({ test("photos upload before Send without a queued spoiler control", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -165,7 +165,7 @@ test("photos upload before Send without a queued spoiler control", async ({ test("opening edit during an immediate photo upload preserves the draft", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -199,7 +199,7 @@ test("opening edit during an immediate photo upload preserves the draft", async }); test("upload a file and see a FileCard in the timeline", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -240,7 +240,7 @@ test("upload a file and see a FileCard in the timeline", async ({ page }) => { test("sends immediately and keeps upload progress across channels", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -286,7 +286,7 @@ test("sends immediately and keeps upload progress across channels", async ({ test("shows upload feedback before transferring a large file", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -373,7 +373,7 @@ test("shows upload feedback before transferring a large file", async ({ test("canceling a background upload prevents the message from publishing", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -395,7 +395,7 @@ test("canceling a background upload prevents the message from publishing", async test("upload progress floats above the dock and lifts Jump to latest", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { const e2e = ( window as Window & { @@ -450,7 +450,7 @@ test("upload progress floats above the dock and lifts Jump to latest", async ({ test("dropping a file on the channel column attaches it to the composer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -524,7 +524,7 @@ for (const theme of ["buzz", "buzz-dark", "github-light", "github-dark"]) { test(`drop prompt has accessible text contrast in ${theme}`, async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate((selectedTheme) => { window.localStorage.setItem("buzz-theme", selectedTheme); }, theme); @@ -583,7 +583,7 @@ test("forum posts emit a FileCard for generic attachments, not a broken image", // and lost its label. The fix routes forum/notes posts through the same // `buildOutgoingMessage` builder as chat. This test would fail (no FileCard) // if ForumComposer ever drifts back to hand-building media markdown. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // "watercooler" is a seeded forum the mock identity is a member of. await page.getByTestId("channel-watercooler").click(); @@ -620,7 +620,7 @@ test("a queued attachment can be removed without a mouse", async ({ page }) => { // Regression: the queued remove badge is revealed on hover, but hiding it // with `display: none` made it unfocusable, leaving keyboard-only users no // way to drop a queued video before sending. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await chooseLargeVideo(page); @@ -662,7 +662,7 @@ test("an uploaded attachment's remove button is named and keyboard-operable", as }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const composer = page.getByTestId("message-composer"); @@ -686,7 +686,7 @@ test("a non-media attachment's remove button is named and keyboard-operable", as }) => { // The file-card branch renders its own remove badge, so it needs the same // accessible name as the image and queued ones. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await chooseQuarterlyReport(page); diff --git a/desktop/tests/e2e/foreground-responsiveness-regression.spec.ts b/desktop/tests/e2e/foreground-responsiveness-regression.spec.ts index 14f19a7bcd..09543e5aa7 100644 --- a/desktop/tests/e2e/foreground-responsiveness-regression.spec.ts +++ b/desktop/tests/e2e/foreground-responsiveness-regression.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installRelayBridge } from "../helpers/bridge"; import { assertRelaySeeded } from "../helpers/seed"; @@ -75,7 +75,7 @@ test("foreground paints and handles a sidebar action before resume fetches", asy ); }); await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/global-agent-config-screenshots.spec.ts b/desktop/tests/e2e/global-agent-config-screenshots.spec.ts index a4fea7fa94..a19b9934ca 100644 --- a/desktop/tests/e2e/global-agent-config-screenshots.spec.ts +++ b/desktop/tests/e2e/global-agent-config-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -11,7 +11,7 @@ const SHOTS = "test-results/global-agent-config"; * `/settings` directly returns a 404 before the client router can start. */ async function openAiDefaultsSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -29,7 +29,7 @@ async function openAiDefaultsSettings(page: import("@playwright/test").Page) { * "New agent" menu item, then fill a placeholder name. */ async function openCreateDialog(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); await page.locator("#persona-display-name").fill("Test Agent"); @@ -704,7 +704,7 @@ test.describe("global agent config screenshots", () => { test("create with a missing name has no footer message", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); @@ -840,7 +840,7 @@ test.describe("global agent config screenshots", () => { }); // Agents view → persona-grouped agent card → Edit quick action. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); const agentButton = page.getByRole("button", { name: "Codex Editor agent profile", @@ -931,7 +931,7 @@ test.describe("global agent config screenshots", () => { }); // Agents view → persona-grouped agent card → Edit quick action. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); const agentButton = page.getByRole("button", { name: "Legacy Editor agent profile", diff --git a/desktop/tests/e2e/harness-catalog-screenshots.spec.ts b/desktop/tests/e2e/harness-catalog-screenshots.spec.ts index 54cb1c067b..46946e9d25 100644 --- a/desktop/tests/e2e/harness-catalog-screenshots.spec.ts +++ b/desktop/tests/e2e/harness-catalog-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "../helpers/test"; +import { test, expect, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -140,7 +140,7 @@ test("after: consolidated harnesses panel + catalog dialog", async ({ test.setTimeout(60_000); await page.setViewportSize({ width: 1280, height: 2400 }); await installMockBridge(page, { acpRuntimesCatalog: CATALOG }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "agents"); await expect(page.getByTestId("settings-harnesses")).toBeVisible({ timeout: 10_000, diff --git a/desktop/tests/e2e/harness-management.spec.ts b/desktop/tests/e2e/harness-management.spec.ts index c69f39c862..4b072f688c 100644 --- a/desktop/tests/e2e/harness-management.spec.ts +++ b/desktop/tests/e2e/harness-management.spec.ts @@ -18,7 +18,7 @@ * - Preset rows render bundled logos, never initials * - Onboarding navigate: setup-page "More harnesses" click → Settings → Agents (F8) */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { passThroughBackupStep } from "../helpers/onboarding"; @@ -130,7 +130,7 @@ function makeCustomEntry( * before the client router starts. */ async function openHarnessSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -667,7 +667,7 @@ test("onboarding setup More-harnesses click navigates to Settings → Agents", a ); window.localStorage.setItem("buzz-active-community-id", communityId); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Reach setup by creating a new identity key and continuing past the // created-key page without opening the optional backup options. diff --git a/desktop/tests/e2e/home-collapsed-top-chrome.spec.ts b/desktop/tests/e2e/home-collapsed-top-chrome.spec.ts index 1a7f941b7e..d37e23847d 100644 --- a/desktop/tests/e2e/home-collapsed-top-chrome.spec.ts +++ b/desktop/tests/e2e/home-collapsed-top-chrome.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -8,7 +8,7 @@ test.describe("home inbox chrome", () => { test.beforeEach(async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); }); diff --git a/desktop/tests/e2e/hosted-communities-settings-screenshots.spec.ts b/desktop/tests/e2e/hosted-communities-settings-screenshots.spec.ts index 075e9a1e0f..a6564cea5d 100644 --- a/desktop/tests/e2e/hosted-communities-settings-screenshots.spec.ts +++ b/desktop/tests/e2e/hosted-communities-settings-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -27,7 +27,7 @@ test.beforeEach(async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "hosted-communities"); }); diff --git a/desktop/tests/e2e/huddle-transcription.spec.ts b/desktop/tests/e2e/huddle-transcription.spec.ts index 8614325434..933c8c73cd 100644 --- a/desktop/tests/e2e/huddle-transcription.spec.ts +++ b/desktop/tests/e2e/huddle-transcription.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { KIND_HUDDLE_ENDED, @@ -122,7 +122,7 @@ test("keeps the drawer open until the huddle is expanded", async ({ page }) => { }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const gradientUnderlay = page.locator(".buzz-theme-gradient-underlay"); const openGradient = await gradientUnderlay.evaluate( @@ -288,7 +288,7 @@ test("floats the in-app huddle tray over the glass background", async ({ }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const root = page.locator("html"); const shell = page.locator('.buzz-huddle-shell[data-huddle-window="false"]'); @@ -360,7 +360,7 @@ test("keeps the popped-out huddle dock full-width over glass", async ({ }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const root = page.locator("html"); const shell = page.locator('.buzz-huddle-shell[data-huddle-window="true"]'); @@ -392,7 +392,7 @@ test("shows speaker identity on every huddle chat message", async ({ }, mock: { threadRepliesDelayMs: 1_500 }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect .poll(() => @@ -488,7 +488,7 @@ test("ignores persisted community onboarding in the huddle room", async ({ }); for (let load = 0; load < 2; load += 1) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("huddle-transcript-intro")).toBeVisible(); await expect( page.getByText("Setting up your community", { exact: true }), @@ -534,7 +534,7 @@ test("keeps main-app shortcuts from navigating the huddle room", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page).toHaveURL( new RegExp(`/channels/${HUDDLE_CHANNEL_ID.replaceAll("-", "\\-")}`), @@ -592,7 +592,7 @@ test("speaks the first eligible agent reply with its participant identity", asyn ttsEnabled: true, }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockLiveSubscription(page, "huddle"); await page.evaluate((agentPubkey) => { @@ -637,7 +637,7 @@ test("animates the responding agent with the shared speaker ring", async ({ ], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentAvatar = page .getByTestId("huddle-participant-strip") @@ -685,7 +685,7 @@ test("stops the speaking agent from the huddle controls", async ({ page }) => { ], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const roomMicButton = page.getByRole("button", { name: "Microphone unavailable", }); @@ -777,7 +777,7 @@ test("assigns distinct agent voices and exposes compact per-agent controls", asy ttsEnabled: true, }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const voiceMenus = page.getByTestId("huddle-agent-voice-menu-trigger"); await expect(voiceMenus).toHaveCount(2); @@ -865,7 +865,7 @@ test("adds channel-mentioned agents to the live huddle roster", async ({ ], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const participantTiles = page.getByTestId("huddle-participant-tile"); await expect(participantTiles).toHaveCount(2); @@ -917,7 +917,7 @@ test("does not enroll available agents when sending an ordinary message", async }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const participantTiles = page.getByTestId("huddle-participant-tile"); await expect(participantTiles).toHaveCount(1); @@ -952,7 +952,7 @@ test("keeps the colored startup surface while huddle controls connect", async ({ members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("huddle-starting-view")).toBeVisible(); await expect( @@ -982,7 +982,7 @@ test("returns the companion transcript to the same channel in the main app", asy transcriptionEnabled: true, }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute( "data-huddle-open", @@ -1049,7 +1049,7 @@ test("returns to the parent channel when leaving a huddle channel in view", asyn members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute( "data-huddle-open", @@ -1087,7 +1087,7 @@ test("keeps the huddle avatar strip compact and exposes the full roster", async members, }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const participantStrip = page.getByTestId("huddle-participant-strip"); const participantTrigger = page.getByRole("button", { @@ -1123,7 +1123,7 @@ test("removes an agent from its menu without showing an extra participant contro ], }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("button", { name: "Manage huddle participants" }), @@ -1166,7 +1166,7 @@ test("keeps a newer huddle event over a delayed hydration snapshot", async ({ }, huddleStateReadDelayMs: 250, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect .poll(() => page.evaluate(() => @@ -1210,7 +1210,7 @@ test("keeps a starting huddle in the drawer after its companion closes", async ( openHuddleWindowDelayMs: 500, startHuddleReturnDelayMs: 1_500, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await page.getByTestId("channel-start-huddle-trigger").click(); @@ -1279,7 +1279,7 @@ test("starts muted with Push to Talk while preserving manual microphone control" openHuddleWindowDelayMs: 10_000, startHuddleReturnDelayMs: 1_500, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await page.getByTestId("channel-start-huddle-trigger").click(); @@ -1390,7 +1390,7 @@ test("toggles the current channel huddle with Control+Shift+Space", async ({ await installMockBridge(page, { openHuddleWindowDelayMs: 10_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("channel-start-huddle-trigger")).toBeEnabled(); const pressHuddleShortcut = () => @@ -1449,7 +1449,7 @@ test("starts an agent DM huddle and hides its backing channel after it ends", as openHuddleWindowDelayMs: 500, startHuddleReturnDelayMs: 1_500, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); await expect @@ -1651,7 +1651,7 @@ test("closes add-agent dialog when parent membership is already satisfied", asyn }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Add agent to huddle" }).click(); const dialog = page.getByRole("dialog", { name: "Add agents" }); @@ -1687,7 +1687,7 @@ test("starts an available stopped agent before adding it to the huddle", async ( }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Add agent to huddle" }).click(); const dialog = page.getByRole("dialog", { name: "Add agents" }); @@ -1738,7 +1738,7 @@ test("stops an agent started solely for a failed huddle add", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Add agent to huddle" }).click(); const dialog = page.getByRole("dialog", { name: "Add agents" }); @@ -1786,7 +1786,7 @@ test("does not deploy a provider agent when its huddle add fails", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Add agent to huddle" }).click(); const dialog = page.getByRole("dialog", { name: "Add agents" }); diff --git a/desktop/tests/e2e/human-edit-agent-content.spec.ts b/desktop/tests/e2e/human-edit-agent-content.spec.ts index 155da6f9f2..319bd70122 100644 --- a/desktop/tests/e2e/human-edit-agent-content.spec.ts +++ b/desktop/tests/e2e/human-edit-agent-content.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -84,7 +84,7 @@ test("owner can edit their owned agent's message", async ({ page }) => { const messageId = `mock-agents-managed-${OWNED_AGENT_PUBKEY.slice(0, 8)}`; const editedContent = `Edited by owner ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -119,7 +119,7 @@ test("owner can edit their owned agent's message", async ({ page }) => { test("owner can delete their owned agent's message", async ({ page }) => { const messageId = `mock-agents-managed-${OWNED_AGENT_PUBKEY.slice(0, 8)}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -149,7 +149,7 @@ test("owner does NOT see Edit or Delete for an unowned agent's message", async ( // Charlie is in mockAgentPubkeys but ownerPubkey is NOT the mock identity. const charlieMessageId = "mock-agents-charlie"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -180,7 +180,7 @@ test("owner can delete their owned agent's message from the thread panel", async // id: `mock-agents-managed-${pubkey.slice(0, 8)}` const messageId = `mock-agents-managed-${OWNED_AGENT_PUBKEY.slice(0, 8)}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -239,7 +239,7 @@ test("owner can edit channel name via owned-agent-owner path", async ({ }) => { const newChannelName = `renamed-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Add OwnedBot as an owner-role member of #random. // In #random the mock identity is only a plain member — selfMember.role !== "owner". @@ -275,7 +275,7 @@ test("owner can edit channel name via owned-agent-owner path", async ({ test("owner does NOT see channel Edit button when no owned agent is a channel owner", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // #random has alice as owner; mock identity is a plain member. // OwnedBot is NOT added as owner in this test — Edit must not appear. diff --git a/desktop/tests/e2e/identity-archive-hide.spec.ts b/desktop/tests/e2e/identity-archive-hide.spec.ts index 819f90daf3..eba72571ad 100644 --- a/desktop/tests/e2e/identity-archive-hide.spec.ts +++ b/desktop/tests/e2e/identity-archive-hide.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, openNewMessagePage } from "../helpers/bridge"; @@ -24,7 +24,7 @@ test.describe("NIP-IA hide archived from discovery", () => { page, }) => { await installMockBridge(page, { archivedIdentities: [ALICE_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("channel-members-trigger").click(); @@ -51,7 +51,7 @@ test.describe("NIP-IA hide archived from discovery", () => { page, }) => { await installMockBridge(page, { archivedIdentities: [] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("channel-members-trigger").click(); await expect(page.getByTestId("members-sidebar")).toBeVisible(); @@ -62,7 +62,7 @@ test.describe("NIP-IA hide archived from discovery", () => { page, }) => { await installMockBridge(page, { archivedIdentities: [ALICE_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -88,7 +88,7 @@ test.describe("NIP-IA hide archived from discovery", () => { page, }) => { await installMockBridge(page, { archivedIdentities: [ALICE_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); @@ -110,7 +110,7 @@ test.describe("NIP-IA hide archived from discovery", () => { // Alice has a seeded message in #general from the prior PR's e2e setup // (see e2eBridge.ts seed). Archiving her must not remove that message. await installMockBridge(page, { archivedIdentities: [ALICE_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -128,7 +128,7 @@ test.describe("NIP-IA hide archived from discovery", () => { // self-exemption is what enforces this — without it, the user would lose // their own seat in the members sidebar. await installMockBridge(page, { archivedIdentities: [SELF_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("channel-members-trigger").click(); await expect(page.getByTestId("members-sidebar")).toBeVisible(); @@ -151,7 +151,7 @@ test.describe("NIP-IA hide archived from discovery", () => { // self from their own autocomplete — that would be the shadowban NIP-IA // exists to prevent. await installMockBridge(page, { archivedIdentities: [SELF_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -174,7 +174,7 @@ test.describe("NIP-IA hide archived from discovery", () => { page, }) => { await installMockBridge(page, { archivedIdentities: [BOB_PUBKEY] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await page.getByTestId("channel-members-trigger").click(); await expect(page.getByTestId("members-sidebar")).toBeVisible(); @@ -191,7 +191,7 @@ test.describe("NIP-IA hide archived from discovery", () => { // companion that makes the test above non-vacuous (proves he was dropped // by the archive filter, not by member-exclusion or a broken search). await installMockBridge(page, { archivedIdentities: [] }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await page.getByTestId("channel-members-trigger").click(); await expect(page.getByTestId("members-sidebar")).toBeVisible(); diff --git a/desktop/tests/e2e/identity-archive.spec.ts b/desktop/tests/e2e/identity-archive.spec.ts index d24d393779..0ccd22f539 100644 --- a/desktop/tests/e2e/identity-archive.spec.ts +++ b/desktop/tests/e2e/identity-archive.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -14,7 +14,7 @@ const ALICE_PUBKEY = "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f"; async function openSelfProfile(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); // First seed message in #general is from the active identity. @@ -24,7 +24,7 @@ async function openSelfProfile(page: import("@playwright/test").Page) { } async function openAliceProfile(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); // Second seed message in #general is from Alice. Her display name "alice" diff --git a/desktop/tests/e2e/identity-key-help.spec.ts b/desktop/tests/e2e/identity-key-help.spec.ts index 15bee1e5d8..40f2a23448 100644 --- a/desktop/tests/e2e/identity-key-help.spec.ts +++ b/desktop/tests/e2e/identity-key-help.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -10,7 +10,7 @@ test("identity key help explains the first-run choice", async ({ page }) => { skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const trigger = page.getByTestId("identity-key-help-trigger"); // No initial opacity-0 assertion: on a slow runner the 2s reveal timer can @@ -65,7 +65,7 @@ test("identity key help stays readable when the app resolves dark mode", async ( skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Fresh profiles follow the system scheme, so the emulated dark scheme is // the first-run repro: the app resolves the dark theme while onboarding. diff --git a/desktop/tests/e2e/identity-lost.spec.ts b/desktop/tests/e2e/identity-lost.spec.ts index dc792f9e3e..59ab7a339e 100644 --- a/desktop/tests/e2e/identity-lost.spec.ts +++ b/desktop/tests/e2e/identity-lost.spec.ts @@ -1,5 +1,5 @@ import { hexToBytes } from "@noble/hashes/utils.js"; -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { nsecEncode } from "nostr-tools/nip19"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -12,7 +12,7 @@ test("normal first launch uses the already-persisted identity", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const gate = page.getByTestId("machine-onboarding-gate"); await expect(gate).toBeVisible(); @@ -58,7 +58,7 @@ test("lost boot opens onboarding gate directly on the key-import page", async ({ { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("machine-onboarding-gate")).toBeVisible(); await expect( @@ -78,7 +78,7 @@ test("lost boot keeps the pairing-code action stable while generating", async ({ { identityLost: true, pairingStartDelayMs: 2_500 }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); const copyButton = page.getByTestId("copy-identity-recovery-code"); @@ -105,7 +105,7 @@ test("lost boot offers phone recovery with a single-use QR", async ({ { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); await expect(page.getByTestId("identity-recovery-pairing")).toBeVisible(); @@ -165,7 +165,7 @@ test("phone recovery uses the desktop pairing card semantics", async ({ { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); const card = page.getByTestId("identity-recovery-pairing"); @@ -235,7 +235,7 @@ test("canceling recovery uses the standard pairing cancellation state", async ({ { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); await expect(page.getByTestId("identity-recovery-qr")).toBeVisible(); @@ -271,7 +271,7 @@ test("phone recovery continues to harness setup without creating or restarting", { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); await expect(page.getByTestId("identity-recovery-qr")).toBeVisible(); @@ -298,7 +298,7 @@ test("recovery turns relay failures into actionable copy", async ({ page }) => { { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); await expect(page.getByTestId("identity-recovery-qr")).toBeVisible(); @@ -326,7 +326,7 @@ test("desktop refreshes recovery codes before the relay expires them", async ({ { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("nostr-import-phone-link").click(); await expect(page.getByTestId("identity-recovery-qr")).toBeVisible(); @@ -352,7 +352,7 @@ test("importing a key from lost mode shows the relaunch-required screen", async { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("heading", { name: "Enter your private key" }), ).toBeVisible(); @@ -373,7 +373,7 @@ test("start-new-identity from lost mode persists the ephemeral key after confirm { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("heading", { name: "Enter your private key" }), ).toBeVisible(); @@ -406,7 +406,7 @@ test("cancelling start-new-identity in lost mode stays on the import screen", as { identityLost: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("heading", { name: "Enter your private key" }), ).toBeVisible(); @@ -429,7 +429,7 @@ test("locked boot shows the keyring-locked screen without the onboarding gate or { identityLocked: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("keyring-locked")).toBeVisible(); await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); @@ -446,7 +446,7 @@ test("locked boot can re-import a key and requires relaunch", async ({ { identityLocked: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("keyring-locked")).toBeVisible(); page.on("dialog", (dialog) => dialog.accept()); @@ -471,7 +471,7 @@ test("locked screen relaunch button records the process-restart invoke", async ( { identityLocked: true }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("keyring-locked")).toBeVisible(); await page.getByTestId("relaunch-app").click(); diff --git a/desktop/tests/e2e/image-attachment-gallery.spec.ts b/desktop/tests/e2e/image-attachment-gallery.spec.ts index 0578155588..5bff135101 100644 --- a/desktop/tests/e2e/image-attachment-gallery.spec.ts +++ b/desktop/tests/e2e/image-attachment-gallery.spec.ts @@ -1,4 +1,4 @@ -import { expect, type Page, test } from "../helpers/test"; +import { expect, type Page, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -118,7 +118,7 @@ test.beforeEach(async ({ page }) => { }); test("image bundle lightbox navigates as a gallery", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -259,7 +259,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => { test("hidden spoiler images are excluded from gallery navigation until revealed", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -352,7 +352,7 @@ test("message images load a thumbnail before requesting the original", async ({ await route.fulfill({ body: svg("#4aa3df"), contentType: "image/svg+xml" }); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await waitForMockLiveSubscription(page, "general"); await page.evaluate( @@ -414,7 +414,7 @@ test("gallery items without imeta dimensions keep their thumbnail aspect ratio", page, }) => { await installNoDimImageRoutes(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -471,7 +471,7 @@ test("forum markdown images use the markdown root as their gallery scope", async page, }) => { await installNoDimImageRoutes(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect .poll(() => { return page.evaluate(() => { @@ -557,7 +557,7 @@ test("multi-image mosaics keep a fixed width and grow by rows", async ({ page, }) => { await installNoDimImageRoutes(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -617,7 +617,7 @@ test("multi-image mosaics keep a fixed width and grow by rows", async ({ test("image mosaic screenshot", async ({ page }) => { await installNoDimImageRoutes(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -662,7 +662,7 @@ test("image mosaic screenshot", async ({ page }) => { test("mosaic image context menu is portaled outside the clipped gallery", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -704,7 +704,7 @@ test("mosaic image context menu is portaled outside the clipped gallery", async test("lightbox image context menu stays inside the dialog focus scope", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -746,7 +746,7 @@ test("lightbox image context menu stays inside the dialog focus scope", async ({ test("right-click image shows Copy image and invokes copy command", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/inbox-edit.spec.ts b/desktop/tests/e2e/inbox-edit.spec.ts index 51b9fe83d5..d1cfe0de6f 100644 --- a/desktop/tests/e2e/inbox-edit.spec.ts +++ b/desktop/tests/e2e/inbox-edit.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -188,7 +188,7 @@ test("editing an immediate attachment reply preserves its media tags", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction( () => @@ -326,7 +326,7 @@ test("Inbox offers Edit and Delete actions only for manageable messages", async page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction( () => @@ -458,7 +458,7 @@ test("cancelling explicit Inbox deletion preserves the message and selection", a page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); const { detail, rootRow } = await seedEmptyDeleteThread(page); const selectedReplyRow = detail.locator( @@ -492,7 +492,7 @@ test("explicit Inbox deletion targets the chosen non-selected message", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); const { detail, rootRow } = await seedEmptyDeleteThread(page); const selectedReplyRow = detail.locator( @@ -526,7 +526,7 @@ test("empty edit confirms deletion of the edited non-selected Inbox row", async page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); const { detail, rootRow } = await seedEmptyDeleteThread(page); const selectedReplyRow = detail.locator( @@ -561,7 +561,7 @@ test("cancelling an empty Inbox edit deletion preserves content and edit mode", page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); const { detail, rootRow } = await seedEmptyDeleteThread(page); const editsBefore = await editCommandCount(page); @@ -601,7 +601,7 @@ test("cold Inbox open drops an edit that was itself deleted", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction(() => { const win = window as MockWindow; diff --git a/desktop/tests/e2e/inbox-live-update.spec.ts b/desktop/tests/e2e/inbox-live-update.spec.ts index 24ad71715a..56234b77d9 100644 --- a/desktop/tests/e2e/inbox-live-update.spec.ts +++ b/desktop/tests/e2e/inbox-live-update.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import type { RelayEvent } from "../../src/shared/api/types"; @@ -282,7 +282,7 @@ test.describe("inbox stable-conversation regressions", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); await installScrollSpy(page); @@ -403,7 +403,7 @@ test.describe("inbox stable-conversation regressions", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); await installScrollSpy(page); @@ -539,7 +539,7 @@ test.describe("inbox stable-conversation regressions", () => { // representative row, selected-message highlight, and parentEventId. await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); @@ -752,7 +752,7 @@ test.describe("inbox stable-conversation regressions", () => { // message highlight, parentEventId — same shape as the primary cold test. await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); @@ -951,7 +951,7 @@ test.describe("inbox stable-conversation regressions", () => { // the newest message). await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); await installScrollSpy(page); @@ -1130,7 +1130,7 @@ test.describe("inbox stable-conversation regressions", () => { // - Assert selected message stays in viewport; spy count stays 1. await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); await installScrollSpy(page); @@ -1340,7 +1340,7 @@ test.describe("inbox stable-conversation regressions", () => { // - Assert selected message stays in viewport; spy count stays 1. await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(getListPane(page)).toBeVisible(); await waitForBridgeReady(page); await installScrollSpy(page); diff --git a/desktop/tests/e2e/inbox-reactions.spec.ts b/desktop/tests/e2e/inbox-reactions.spec.ts index 348176ea6e..6407499ee4 100644 --- a/desktop/tests/e2e/inbox-reactions.spec.ts +++ b/desktop/tests/e2e/inbox-reactions.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import type { RelayEvent } from "../../src/shared/api/types"; @@ -37,7 +37,7 @@ test("inbox reaction on a thread-reply mention persists after refetch", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction(() => { const win = window as MockWindow; diff --git a/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts b/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts index b7815cb324..a6c91ee6db 100644 --- a/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts +++ b/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts @@ -11,7 +11,7 @@ * Output: test-results/inbox-refactor/ */ import { type Page } from "@playwright/test"; -import { expect, test } from "../helpers/test"; +import { bootstrapE2ePage, expect, test } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -214,7 +214,7 @@ test.describe("inbox refactor screenshots", () => { await seedTwoDrafts(page); await installMockBridge(page, { mode: "mock" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("home-inbox")).toBeVisible({ timeout: 10_000, }); @@ -235,7 +235,7 @@ test.describe("inbox refactor screenshots", () => { }) => { await installMockBridge(page, { mode: "mock" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("home-inbox")).toBeVisible({ timeout: 10_000, }); @@ -262,7 +262,7 @@ test.describe("inbox refactor screenshots", () => { }) => { await installMockBridge(page, { mode: "mock" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForMockFeedHelpers(page); const dmIds = ["shot-dm-first", "shot-dm-second", "shot-dm-third"]; @@ -332,7 +332,7 @@ test.describe("inbox refactor screenshots", () => { await seedConversationPreferences(page, "default", "comfortable"); await installMockBridge(page, { mode: "mock" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForMockFeedHelpers(page); const replyIds = [ diff --git a/desktop/tests/e2e/inline-custom-harness.spec.ts b/desktop/tests/e2e/inline-custom-harness.spec.ts index 43ed5f723c..98797b1525 100644 --- a/desktop/tests/e2e/inline-custom-harness.spec.ts +++ b/desktop/tests/e2e/inline-custom-harness.spec.ts @@ -14,7 +14,7 @@ * selection untouched. The three surfaces share the same routing, so those * checks are not repeated on every one. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -47,7 +47,7 @@ async function registerHarness(page: Page) { /** Open the create-agent dialog (AgentDefinitionDialog, create mode). */ async function openCreateDialog(page: Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); const dialog = page.getByTestId("persona-dialog"); @@ -58,7 +58,7 @@ async function openCreateDialog(page: Page) { /** Open the edit dialog for a saved definition (same dialog, edit mode). */ async function openDefinitionEditDialog(page: Page, name: string) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agents-library-personas")).toBeVisible({ timeout: 10_000, @@ -159,7 +159,7 @@ test.describe("inline add custom harness", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-agents-view").click(); await page .getByRole("button", { name: "Instance Agent agent profile" }) diff --git a/desktop/tests/e2e/integration.spec.ts b/desktop/tests/e2e/integration.spec.ts index f1f71cd381..b8f7928f30 100644 --- a/desktop/tests/e2e/integration.spec.ts +++ b/desktop/tests/e2e/integration.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Browser } from "../helpers/test"; +import { expect, test, type Browser, bootstrapE2ePage } from "../helpers/test"; import { installRelayBridge, @@ -184,7 +184,7 @@ test("create channel and verify in sidebar", async ({ page }) => { const channelName = `integration-e2e-${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page.getByTestId("create-channel-submit").click(); @@ -383,7 +383,7 @@ test("live forum mentions refetch the home feed without waiting for polling", as test("DM channel appears in sidebar", async ({ page }) => { await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("dm-list")).toContainText("alice-tyler"); }); @@ -392,7 +392,7 @@ test("send message to DM", async ({ page }) => { const message = `DM message ${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); @@ -404,7 +404,7 @@ test("send message to DM", async ({ page }) => { test("forum channel appears in sidebar", async ({ page }) => { await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("forum-list")).toContainText("watercooler"); }); @@ -414,7 +414,7 @@ test("create channel with description", async ({ page }) => { const description = `Description for ${channelName}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await createStream(page, channelName, description); await expect(page.getByTestId("chat-title")).toHaveAttribute( @@ -429,7 +429,7 @@ test("multiple channels independent", async ({ page }) => { const messageA = `Message in A ${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelA); @@ -466,7 +466,7 @@ test("manage sheet updates channel details through the relay", async ({ const updatedDescription = `Updated description ${stamp}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await createStream(page, initialName, initialDescription); await openChannelManagement(page); @@ -527,7 +527,7 @@ test("manage sheet archive and unarchive survives a reload through the relay", a const channelName = `archive-integration-${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await createStream(page, channelName, "Archive integration channel"); await openChannelManagement(page); diff --git a/desktop/tests/e2e/invite-link-copy.spec.ts b/desktop/tests/e2e/invite-link-copy.spec.ts index 2e1f374a25..e14c982cea 100644 --- a/desktop/tests/e2e/invite-link-copy.spec.ts +++ b/desktop/tests/e2e/invite-link-copy.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -30,7 +30,7 @@ test.beforeEach(async ({ page }) => { test("copies a freshly minted invite link from the link field", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "community-members"); await expect(page.getByTestId("settings-community-members")).toBeVisible(); @@ -68,7 +68,7 @@ test("copies a freshly minted invite link from the link field", async ({ }); test("sets a selected invite-use limit", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "community-members"); await page.getByTestId("community-invite-dialog-trigger").click(); @@ -112,7 +112,7 @@ test("retries a failed invite-link generation", async ({ page }) => { }); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "community-members"); await page.getByTestId("community-invite-dialog-trigger").click(); @@ -136,7 +136,7 @@ test("retries a failed invite-link generation", async ({ page }) => { test("reopens with the default expiry before generating a new link", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "community-members"); await page.getByTestId("community-invite-dialog-trigger").click(); diff --git a/desktop/tests/e2e/invites-settings-screenshots.spec.ts b/desktop/tests/e2e/invites-settings-screenshots.spec.ts index aff44b077e..c2523bc54c 100644 --- a/desktop/tests/e2e/invites-settings-screenshots.spec.ts +++ b/desktop/tests/e2e/invites-settings-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -32,7 +32,7 @@ test.beforeEach(async ({ page }, testInfo) => { status: 200, }); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "community-members"); }); diff --git a/desktop/tests/e2e/key-import-reveal.spec.ts b/desktop/tests/e2e/key-import-reveal.spec.ts index 091684d958..4f387dddb6 100644 --- a/desktop/tests/e2e/key-import-reveal.spec.ts +++ b/desktop/tests/e2e/key-import-reveal.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -16,7 +16,7 @@ test("key import masks the key with a reveal toggle", async ({ page }) => { skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); const input = page.getByTestId("nostr-import-nsec-input"); diff --git a/desktop/tests/e2e/live-broadcast-reply-timeline.spec.ts b/desktop/tests/e2e/live-broadcast-reply-timeline.spec.ts index a59bfbb879..792b459099 100644 --- a/desktop/tests/e2e/live-broadcast-reply-timeline.spec.ts +++ b/desktop/tests/e2e/live-broadcast-reply-timeline.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -101,7 +101,7 @@ test("a live broadcast depth-1 reply enters the authoritative channel window sto page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/local-archive-screenshots.spec.ts b/desktop/tests/e2e/local-archive-screenshots.spec.ts index a5ccc5ecf0..183a4059f6 100644 --- a/desktop/tests/e2e/local-archive-screenshots.spec.ts +++ b/desktop/tests/e2e/local-archive-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -9,7 +9,7 @@ const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"; // Navigate to the Local Archive settings panel. async function openLocalArchiveSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); diff --git a/desktop/tests/e2e/markdown-parse-cache.spec.ts b/desktop/tests/e2e/markdown-parse-cache.spec.ts index e6600e225c..ec35a0517f 100644 --- a/desktop/tests/e2e/markdown-parse-cache.spec.ts +++ b/desktop/tests/e2e/markdown-parse-cache.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -35,7 +35,7 @@ test("warm channel switches trigger zero fresh markdown parses", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_MD_PARSE_COUNT__ === "function", ); diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index a92a5ccbf3..38e700056b 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, @@ -276,7 +276,7 @@ test("@ trigger prioritizes channel members before runnable personas and other m }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -323,7 +323,7 @@ test("@ trigger prioritizes channel members before runnable personas and other m test("relay-only shared agents emit an outbound mention tag when selected", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -373,7 +373,7 @@ test("thread autocomplete keeps multiple long names readable in a narrow panel", await page.addInitScript(() => { window.sessionStorage.setItem("buzz.desktop.thread-panel-width", "300"); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.setViewportSize({ width: 760, height: 640 }); @@ -422,7 +422,7 @@ test("blocks non-participant persona mentions in DM threads", async ({ await installMockBridge(page, { activePersonaIds: ["builtin:fizz"], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-bob-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("bob-tyler"); await waitForMockLiveSubscription(page, "bob-tyler"); @@ -482,7 +482,7 @@ test("defers agent mentions until DM members finish loading", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); await waitForMockLiveSubscription(page, "alice-tyler"); @@ -544,7 +544,7 @@ test("autocomplete filters managed-agent suggestions as user types", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -567,7 +567,7 @@ test("autocomplete searches global non-member people from the first typed charac }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -589,7 +589,7 @@ test("mention autocomplete caps global people search at 50 results", async ({ })); await installMockBridge(page, { searchProfiles }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -623,7 +623,7 @@ test("mention autocomplete caps global people search at 50 results", async ({ test("selecting a person mention inserts @Name into input", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -655,7 +655,7 @@ test("selecting a person mention inserts @Name into input", async ({ test("channel references keep caret movement through the channel name", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -698,7 +698,7 @@ test("selecting a managed agent mention inserts @Name into input", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -724,7 +724,7 @@ test("selecting a persona mention creates a channel agent before sending", async await installMockBridge(page, { activePersonaIds: ["builtin:fizz"], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -813,7 +813,7 @@ test("selecting a persona mention reuses an existing persona agent", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -877,7 +877,7 @@ test("managed relay-profile agents with member roles use the agent composer styl }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openChannelBrowser(page); await expect(page.getByTestId("channel-browser-dialog")).toBeVisible(); @@ -916,7 +916,7 @@ test("other-owned agents without a shared channel are hidden from mentions", asy ], userSearchDelayMs: 1_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -932,7 +932,7 @@ test("stale channel-member agents absent from managed and relay directories stay page, }) => { await installMockBridge(page, { userSearchDelayMs: 1_000 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -962,7 +962,7 @@ test("managed relay agents are visible in channel mentions regardless of relay p }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -977,7 +977,7 @@ test("managed relay agents are visible in channel mentions regardless of relay p test("relay-only shared agents stay hidden from DM mentions", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); @@ -990,7 +990,7 @@ test("cached relay-agent suggestions are removed when channel authorization disa page, }) => { await installMockBridge(page, { userSearchDelayMs: 10_000 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1037,7 +1037,7 @@ test("relay-only shared agents appear in forum mentions", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-watercooler").click(); await expect(page.getByTestId("chat-title")).toHaveText("watercooler"); await page.getByRole("button", { name: "Start a new post..." }).click(); @@ -1074,7 +1074,7 @@ test("forum sends revalidate relay-agent authorization before signing", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-watercooler").click(); await expect(page.getByTestId("chat-title")).toHaveText("watercooler"); await page.getByRole("button", { name: "Start a new post..." }).click(); @@ -1131,7 +1131,7 @@ test("relay-only allowlisted agents are visible in channel mentions", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1158,7 +1158,7 @@ test("relay-agent directory errors fail closed and recover after a fresh fetch", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill("@quinn"); @@ -1196,7 +1196,7 @@ test("relay-only allowlisted agents emit a p tag when sent", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.evaluate( async ({ channelId, pubkey }) => { @@ -1261,7 +1261,7 @@ test("managed agents keep their p tag when relay discovery fails before send", a }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -1300,7 +1300,7 @@ test("targeted revocation before send causes no agent side effects", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.evaluate( async ({ channelId, pubkey }) => { @@ -1373,7 +1373,7 @@ test("selected relay agents revoked after the invite prompt cause no side effect }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill("@quinn"); @@ -1430,7 +1430,7 @@ test("selected relay agents revoked during send emit no p tag", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill("@quinn"); @@ -1481,7 +1481,7 @@ test("owner-only builds hide other-owned relay agents", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill("@quinn"); @@ -1511,7 +1511,7 @@ test("owner-only builds show verified same-owner relay agents", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill("@quinn"); @@ -1532,7 +1532,7 @@ test("relay-only allowlisted agents stay hidden outside their channel", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1554,7 +1554,7 @@ test("relay-only anyone agents are visible when a channel is shared", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1577,7 +1577,7 @@ test("relay-only excluded agents stay hidden from channel mentions", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1601,7 +1601,7 @@ test("shared agents wait for initial directory authorization", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1626,7 +1626,7 @@ test("mentioning an in-channel stopped managed agent starts it before sending", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1676,7 +1676,7 @@ test("mentioning an in-channel provider managed agent deploys it before sending" }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1720,7 +1720,7 @@ test("mentioning a non-member managed agent adds and starts it before sending", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1781,7 +1781,7 @@ test("mentioning a non-member provider managed agent deploys it before sending", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1842,7 +1842,7 @@ test("system add rows use plain names while remove rows retain agent mention sty }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general", SYSTEM_MESSAGE_KIND); @@ -1911,7 +1911,7 @@ test("groups contiguous arrival activity with hidden names in the standard toolt await installMockBridge(page, { searchProfiles: [actor, ...targets], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general", SYSTEM_MESSAGE_KIND); @@ -1986,7 +1986,7 @@ test("groups contiguous arrival activity with hidden names in the standard toolt }); test("system agent profile exposes owned agent actions", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general", SYSTEM_MESSAGE_KIND); @@ -2031,7 +2031,7 @@ test("system agent profile exposes owned agent actions", async ({ page }) => { }); test("system agent activity avatar stack is decorative", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); await waitForMockLiveSubscription(page, "random", SYSTEM_MESSAGE_KIND); @@ -2067,7 +2067,7 @@ test("system agent activity avatar stack is decorative", async ({ page }) => { test("membership activity folds a member joining then leaving", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-random").click(); await expect(page.getByTestId("chat-title")).toHaveText("random"); await waitForMockLiveSubscription(page, "random", SYSTEM_MESSAGE_KIND); @@ -2117,7 +2117,7 @@ test("profile-only agent author hides actions without agent access", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2146,7 +2146,7 @@ test("profile-only agent author hides actions without agent access", async ({ test("system member-joined rows render the joined person as a plain profile name", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general", SYSTEM_MESSAGE_KIND); @@ -2189,7 +2189,7 @@ test("selecting a managed non-member agent from a DM inserts @Name into input", }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-bob-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("bob-tyler"); @@ -2209,7 +2209,7 @@ test("selecting a managed non-member agent from a DM inserts @Name into input", test("global non-member people can be selected from channel mentions", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2237,7 +2237,7 @@ test("duplicate global people with the same visible identity collapse in channel }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2251,7 +2251,7 @@ test("duplicate global people with the same visible identity collapse in channel test("sent non-member person mention uses the normal mention style", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-bob-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("bob-tyler"); @@ -2284,7 +2284,7 @@ test("sent managed non-member agent mention uses the agent mention style", async }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-bob-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("bob-tyler"); @@ -2309,7 +2309,7 @@ test("sent managed non-member agent mention uses the agent mention style", async test("mention button opens autocomplete and inserts a selected member", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2332,7 +2332,7 @@ test("inserting a mention preserves Shift+Enter newlines (regression: bug #2)", // break to a single space. After the fix, autocomplete uses a // native ProseMirror `tr.insertText` transaction and the line // breaks survive. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2353,7 +2353,7 @@ test("inserting a mention preserves Shift+Enter newlines (regression: bug #2)", }); test("keyboard navigation selects mention with Enter", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2371,7 +2371,7 @@ test("keyboard navigation selects mention with Enter", async ({ page }) => { }); test("Escape dismisses autocomplete dropdown", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2389,7 +2389,7 @@ test("Escape dismisses autocomplete dropdown", async ({ page }) => { test("mention text is highlighted in sent messages", async ({ page }) => { const suffix = ` check this out ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2412,7 +2412,7 @@ test("mention text is highlighted in sent messages", async ({ page }) => { }); test("clicking author name opens user profile panel", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2432,7 +2432,7 @@ test("clicking author name opens user profile panel", async ({ page }) => { test("hovering avatar opens popover, clicking opens profile panel", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2455,7 +2455,7 @@ test("hovering avatar opens popover, clicking opens profile panel", async ({ test("clicking a mention chip in the timeline opens the profile panel", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2483,7 +2483,7 @@ test("mention text matching the kind-0 name alias resolves and opens the profile // bob's mock profile has display_name "bob" and kind-0 name "bobby". A // message that says "@bobby" (how agents/CLI resolve mentions at send time) // must still render a clickable chip bound to bob's pubkey. - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2508,7 +2508,7 @@ test("mention text matching the kind-0 name alias resolves and opens the profile test("clicking a mention chip in a forum post opens the profile panel", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Seed the forum post before entering the channel — forum views load from // the mock store on fetch, so no live subscription is needed. await page.getByTestId("channel-general").click(); @@ -2545,7 +2545,7 @@ test("agent profile popover shows its owner", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2585,7 +2585,7 @@ test("agent profile popover labels an agent owned by the viewer as you", async ( }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2625,7 +2625,7 @@ test("agent profile popover falls back to the owner's pubkey", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2653,7 +2653,7 @@ test("agent profile popover falls back to the owner's pubkey", async ({ }); test("human profile popover does not show an owner", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2690,7 +2690,7 @@ test("owned bot profile exposes message and huddle actions", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -2738,7 +2738,7 @@ test("owned agent mention profile exposes message and huddle actions", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2770,7 +2770,7 @@ test("profile popover wave sends a direct message for a human profile", async ({ }) => { await installMockBridge(page, { sendMessageDelayMs: 2_500 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -2864,7 +2864,7 @@ test("delayed inaccessible agent profile keeps all actions hidden", async ({ ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); diff --git a/desktop/tests/e2e/mesh-compute.spec.ts b/desktop/tests/e2e/mesh-compute.spec.ts index e6aac193a3..e54998cd34 100644 --- a/desktop/tests/e2e/mesh-compute.spec.ts +++ b/desktop/tests/e2e/mesh-compute.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -18,7 +18,7 @@ type E2eWindow = Window & { test("Share compute chooses a model before sharing", async ({ page }) => { const modelRef = "hf://demo/SmolLM2-135M-Instruct-GGUF:Q4_K_M"; await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "compute"); const card = page.getByTestId("settings-mesh-share-compute"); @@ -102,7 +102,7 @@ test("a consuming client can switch to sharing its saved local model", async ({ window.localStorage.setItem("buzz.mesh-compute.share.model.v1", model); }, localModel); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // The mesh seed hook is installed when the mock bridge boots; calling it // before then silently no-ops (optional chaining) and the seed is lost. await page.waitForFunction( diff --git a/desktop/tests/e2e/message-feedback-snapshots.spec.ts b/desktop/tests/e2e/message-feedback-snapshots.spec.ts index f23bbc3795..b5a93d311b 100644 --- a/desktop/tests/e2e/message-feedback-snapshots.spec.ts +++ b/desktop/tests/e2e/message-feedback-snapshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -31,7 +31,7 @@ test("pending continuation keeps Sending next to its timestamp", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -96,7 +96,7 @@ test("pending continuation keeps Sending next to its timestamp", async ({ test("profile hover uses the channel hover surface", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const profile = page.getByTestId("sidebar-profile-card"); const channel = page.getByTestId("channel-random"); diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts index a5fffd9112..db92fcfe83 100644 --- a/desktop/tests/e2e/messaging.spec.ts +++ b/desktop/tests/e2e/messaging.spec.ts @@ -1,6 +1,6 @@ import { readFileSync } from "node:fs"; -import { expect, test, type Locator } from "../helpers/test"; +import { expect, test, type Locator, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -379,7 +379,7 @@ test.beforeEach(async ({ page }, testInfo) => { }); test("agent owner label identifies the agent and owner", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const aliceMessage = page @@ -401,7 +401,7 @@ test("agent owner label identifies the agent and owner", async ({ page }) => { test("send a message and see it in timeline", async ({ page }) => { const message = `Hello timeline ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -417,7 +417,7 @@ test("send a message and see it in timeline", async ({ page }) => { test("long autolink wraps without widening the timeline", async ({ page }) => { await page.setViewportSize({ width: 800, height: 600 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -458,7 +458,7 @@ test("markdown tables overflow wide content and fill the message when narrow", a page, }) => { await page.setViewportSize({ width: 900, height: 600 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.waitForFunction( @@ -531,7 +531,7 @@ test("sent link preview media uses the authenticated proxy in compact and rich c }), ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); await waitForReadyComposerSnapshots(page); @@ -596,7 +596,7 @@ test("link preview style defaults to compact and Rich unfurls descriptions", asy }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?inline=1"; await page.setViewportSize({ width: 800, height: 900 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); const composerPreview = page @@ -706,7 +706,7 @@ for (const [pasteShape, wrapUrl] of [ page, }) => { const previewUrl = `https://github.com/block/buzz/pull/3246?paste=${pasteShape}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.focus(); @@ -752,7 +752,7 @@ test("display-text link preview produces and sends its preview", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?display=text"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -794,7 +794,7 @@ test("rich link preview preserves description newlines after sending", async ({ await page.addInitScript(() => localStorage.setItem("buzz.appearance.linkPreviewStyle", "rich"), ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); await waitForReadyComposerSnapshots(page); @@ -828,7 +828,7 @@ test("completed link previews normalize a trailing-fragment URL and still send", "https://x.com/tellaho/status/1884289176381841506", ]; const pastedText = previewUrls.join("\n"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.focus(); @@ -874,7 +874,7 @@ test("unresolvable preview disappears after the terminal miss", async ({ page, }) => { const previewUrl = "https://x.com/tellaho/status"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); @@ -895,7 +895,7 @@ test("explicit cancellation suppresses a pending link preview and sends without page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?send=pending"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); @@ -969,7 +969,7 @@ test("Enter during an in-flight snapshot upload hands off and sends once", async page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1009,7 +1009,7 @@ test("async metadata beyond old cutoff still produces preview image", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?slow=metadata"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1032,7 +1032,7 @@ test("async upload beyond metadata budget retains preview image", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?slow=image"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1063,7 +1063,7 @@ test("Skip wins the upload race and sends without preview", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?skip=1"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1095,7 +1095,7 @@ test("promoted link preview send clears Sending after REST publication", async ( page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?pending=preview"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1122,7 +1122,7 @@ test("settled-empty promoted link preview send uses REST and clears Sending afte page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?pending=empty"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1195,7 +1195,7 @@ test("draft auto-send promotes link preview preparation and sends exactly once", // Drive the real Drafts-panel "Send message" confirm flow. This does an // in-app client navigation to the channel with ?autoSend=, arming // the main composer's auto-submit effect — the exact production path. - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("home-inbox")).toBeVisible({ timeout: 10_000 }); await page.getByTestId("inbox-filter-trigger").click(); await page.getByRole("menuitemradio", { name: "Drafts" }).click(); @@ -1238,7 +1238,7 @@ test("rapid Enter presses on a ready link preview send exactly once", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?rapid=1"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1275,7 +1275,7 @@ test("pasting a link and immediately pressing Enter prepares it after submit", a page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?fast=send"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -1299,7 +1299,7 @@ test("a snapshot media upload failure preserves a metadata-only preview", async page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?upload=fail"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); await input.fill(previewUrl); @@ -1330,7 +1330,7 @@ test("a snapshot media upload failure preserves a metadata-only preview", async test("editing a message excludes link previews entirely", async ({ page }) => { const message = `Edit-me ${Date.now()}`; const previewUrl = "https://github.com/block/buzz/pull/3246?edit=1"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const input = page.getByTestId("message-input"); @@ -1370,7 +1370,7 @@ test("hiding composer link previews suppresses the whole draft and emits the bla }) => { const firstUrl = "https://github.com/block/buzz/pull/3246?hide=all"; const secondUrl = "https://linear.app/acme/issue/ABC-123/hidden-too"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(firstUrl); await expect(page.locator("[data-composer-link-previews]")).toBeVisible(); @@ -1407,7 +1407,7 @@ test("composer link preview embeds stay attachment-sized while loading and ready for (const width of [800, 420]) { await page.setViewportSize({ width: 800, height: 700 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.setViewportSize({ width, height: 700 }); await page @@ -1479,7 +1479,7 @@ test("compact link preview image geometry truncates long titles to one line", as }), ); await page.setViewportSize({ width: 800, height: 700 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); await waitForReadyComposerSnapshots(page); @@ -1518,7 +1518,7 @@ test("composer no-image link embeds keep the attachment footprint", async ({ page, }) => { const previewUrl = "https://github.com/block/buzz/pull/3246?inline=none"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("message-input").fill(previewUrl); const card = page @@ -1537,7 +1537,7 @@ test("mixed link preview image outcomes keep Compact and Rich fallbacks stable", }) => { const loadedUrl = "https://github.com/block/buzz/pull/4001"; const rateLimitedUrl = "https://github.com/block/buzz/pull/4002"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page .getByTestId("message-input") @@ -1586,7 +1586,7 @@ test("fragment link previews render a card per canonical URL", async ({ "https://github.com/block/buzz/pull/3767#pullrequestreview-4857569498"; const fragmentUrlB = "https://github.com/block/buzz/pull/3767#issuecomment-1"; const plainUrl = "https://github.com/block/buzz/pull/3867"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page .getByTestId("message-input") @@ -1615,7 +1615,7 @@ test("fragment link previews render a card per canonical URL", async ({ test("link preview browser image errors render a fallback", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page .getByTestId("message-input") @@ -1648,7 +1648,7 @@ test("supported Compact link previews keep the message link visible with square }) => { const previewUrl = "https://github.com/block/sprout/pull/1334"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1675,7 +1675,7 @@ test("send multiple messages in sequence", async ({ page }) => { const input = page.getByTestId("message-input"); const sendButton = page.getByTestId("send-message"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1700,7 +1700,7 @@ test("copy a rendered code block and paste it back as code", async ({ const code = "# not a heading\nconst answer = 42;\n indented();"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1746,7 +1746,7 @@ test("pasting a long copied code block scrolls composer to cursor", async ({ (_, index) => `const line${index} = ${index};`, ).join("\n"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1785,7 +1785,7 @@ test("code block shows language label when language is specified", async ({ origin: "http://127.0.0.1:4173", }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1807,7 +1807,7 @@ test("code block shows language label when language is specified", async ({ test("typing triple backticks and Enter creates a code block in composer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1828,7 +1828,7 @@ test("message input clears after send", async ({ page }) => { const message = `Clear after send ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1843,7 +1843,7 @@ test("message input clears after send", async ({ page }) => { test("emoji picker inserts emoji into the draft and keeps focus in the composer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1867,7 +1867,7 @@ test("emoji picker inserts emoji into the draft and keeps focus in the composer" }); test("empty message cannot be sent", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1879,7 +1879,7 @@ test("send message with Enter key", async ({ page }) => { const message = `Enter key send ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1892,7 +1892,7 @@ test("send message with Enter key", async ({ page }) => { test("messages persist across channel switches", async ({ page }) => { const message = `Persist across switch ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1912,7 +1912,7 @@ test("draft is preserved when switching channels", async ({ page }) => { const draft = `Unsent draft ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1935,7 +1935,7 @@ test("sending a message clears the draft", async ({ page }) => { const message = `Sent message ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -1957,7 +1957,7 @@ test("different channels have independent messages", async ({ page }) => { const generalMessage = `General only ${ts}`; const randomMessage = `Random only ${ts}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("message-input").fill(generalMessage); @@ -1989,7 +1989,7 @@ test("different channels have independent messages", async ({ page }) => { }); test("day divider appears in timeline", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2002,7 +2002,7 @@ test("day divider appears in timeline", async ({ page }) => { test("send message to DM channel p-tags the recipient", async ({ page }) => { const message = `DM message ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); @@ -2074,7 +2074,7 @@ test("sends a thread message to its parent channel with a root-thread link", asy }), ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.waitForFunction( @@ -2314,7 +2314,7 @@ test("shows your avatar on your own message when profile avatar is set", async ( const avatarUrl = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"%3E%3Crect width="16" height="16" rx="4" fill="%2300a36c"/%3E%3C/svg%3E'; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); await page.getByTestId("profile-avatar-url").fill(avatarUrl); @@ -2348,7 +2348,7 @@ test("opens a single-level thread panel with inline expansion", async ({ (_, index) => `Thread filler reply ${index} ${timestamp}`, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("message-timeline")).toContainText( @@ -2632,7 +2632,7 @@ test("thread panel width uses session storage and reset handle", async ({ ); }, customWidthPx); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2689,7 +2689,7 @@ test("narrow thread view collapses channel header actions into a menu", async ({ }) => { await page.setViewportSize({ width: 980, height: 720 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("channel-add-bot-trigger")).toHaveCount(0); @@ -2734,7 +2734,7 @@ test("narrow thread view collapses channel header actions into a menu", async ({ test("single-panel thread view hides channel actions", async ({ page }) => { await page.setViewportSize({ width: 860, height: 720 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("channel-add-bot-trigger")).toHaveCount(0); @@ -2755,7 +2755,7 @@ test("single-panel thread view hides channel actions", async ({ page }) => { }); test("composer is focused after selecting a channel", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2770,7 +2770,7 @@ test("composer is focused after selecting a channel", async ({ page }) => { test("composer is focused after switching to a different channel", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2784,7 +2784,7 @@ test("composer is focused after switching to a different channel", async ({ test("thread composer is focused after clicking the reply icon", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2816,7 +2816,7 @@ test("thread refetch preserves a live reply and reaction received in flight", as page, }) => { await installMockBridge(page, { threadRepliesDelayMs: 800 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2873,7 +2873,7 @@ test("thread reply appears after relay closes and restores its live subscription page, }) => { await installMockBridge(page, { closeChannelLiveSubscriptionOnce: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2901,7 +2901,7 @@ test("thread reply appears after relay closes and restores its live subscription test("thread composer keeps focus after sending a thread reply", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2961,7 +2961,7 @@ test("ArrowUp in an empty composer edits your last message right after sending", const message = `Edit-last via arrow up ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2990,7 +2990,7 @@ test("ArrowUp does not edit when the composer has draft text", async ({ const draft = `Half-typed draft ${Date.now()}`; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -3014,7 +3014,7 @@ test("ArrowUp edits your last thread reply right after sending it", async ({ const seed = `Thread arrow-up seed ${Date.now()}`; const reply = `Thread reply to edit ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -3056,7 +3056,7 @@ test("action bar stays within the timeline when the thread panel is open", async // or the right-anchored action bar is pushed offscreen (regression: #1081 // fixed the wrap but rows still expanded to content min-width). await page.setViewportSize({ width: 1024, height: 800 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/mobile-pairing-qr.spec.ts b/desktop/tests/e2e/mobile-pairing-qr.spec.ts index ac4b653b24..a2743fee51 100644 --- a/desktop/tests/e2e/mobile-pairing-qr.spec.ts +++ b/desktop/tests/e2e/mobile-pairing-qr.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { mkdirSync } from "node:fs"; import { installMockBridge } from "../helpers/bridge"; @@ -38,7 +38,7 @@ async function emitPairingEvent(page: Page, event: string, payload?: unknown) { test("mobile pairing starts on demand and reveals the QR code", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await page.getByTestId("settings-nav-mobile").click(); @@ -231,7 +231,7 @@ test("mobile pairing starts on demand and reveals the QR code", async ({ test("pairing completion updates the final step and resets after leaving", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await page.getByTestId("settings-nav-mobile").click(); @@ -397,7 +397,7 @@ test("pairing completion updates the final step and resets after leaving", async }); test("late pairing events are ignored after canceling", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await page.getByTestId("settings-nav-mobile").click(); @@ -432,7 +432,7 @@ test("late pairing events are ignored after canceling", async ({ page }) => { test("step completion respects reduced motion", async ({ page }) => { await page.emulateMedia({ reducedMotion: "reduce" }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await page.getByTestId("settings-nav-mobile").click(); diff --git a/desktop/tests/e2e/navigation.spec.ts b/desktop/tests/e2e/navigation.spec.ts index 4ed304b295..3dd8a78e3f 100644 --- a/desktop/tests/e2e/navigation.spec.ts +++ b/desktop/tests/e2e/navigation.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -13,7 +13,7 @@ test.beforeEach(async ({ page }) => { }); async function navigateToWorkflows(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-workflows-view").click(); await expect(page).toHaveURL(/#\/workflows$/); await expect(page.getByTestId("workflows-view")).toBeVisible(); @@ -33,7 +33,7 @@ async function createWorkflow( } test("global back and forward move across channel routes", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -55,7 +55,7 @@ test("back/forward keyboard chords work while the composer has focus", async ({ const forwardChord = process.platform === "darwin" ? "Meta+]" : "Alt+ArrowRight"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -86,7 +86,8 @@ test("back/forward keyboard chords work while the composer has focus", async ({ test.fixme("direct forum thread links close back to the forum route", async ({ page, }) => { - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${WATERCOLOR_CHANNEL_ID}/posts/${FORUM_POST_ID}`, ); @@ -122,7 +123,7 @@ test("direct workflow detail links close back to workflows", async ({ expect(workflowId).toBeTruthy(); - await page.goto(`/#/workflows/${workflowId}`); + await bootstrapE2ePage(page, `/#/workflows/${workflowId}`); await expect(page.getByTestId("workflow-detail-panel")).toBeVisible(); await page.getByRole("button", { name: "Close detail panel" }).click(); @@ -132,7 +133,8 @@ test("direct workflow detail links close back to workflows", async ({ }); test("forum reply deep links survive reload", async ({ page }) => { - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${WATERCOLOR_CHANNEL_ID}/posts/${FORUM_POST_ID}?replyId=${FORUM_REPLY_ID}`, ); @@ -150,7 +152,7 @@ test("forum reply deep links survive reload", async ({ page }) => { }); test("back and forward restore open thread panels", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -180,7 +182,7 @@ test("back and forward restore open thread panels", async ({ page }) => { }); test("back undoes closing a thread panel", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -203,7 +205,7 @@ test("back undoes closing a thread panel", async ({ page }) => { }); test("open thread panels survive reload", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -228,7 +230,7 @@ test("open thread panels survive reload", async ({ page }) => { test("home inbox selection survives reload and back restores it", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const inboxList = page.getByTestId("home-inbox-list"); await expect(inboxList).toBeVisible(); @@ -265,7 +267,7 @@ test("home inbox selection survives reload and back restores it", async ({ test("settings is a route: section survives reload, closing returns to the previous panel state", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Open a channel with a thread panel so there's panel state to come back to. await page.getByTestId("channel-general").click(); @@ -300,7 +302,7 @@ test("settings is a route: section survives reload, closing returns to the previ test("settings shortcut returns without opening search dialog", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); const channelUrl = page.url(); @@ -340,7 +342,7 @@ test("settings shortcut returns without opening search dialog", async ({ test("mixed Buzz permalinks render as chips in the composer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -395,7 +397,7 @@ test("mixed Buzz permalinks render as chips in the composer", async ({ test("message links to visible root messages open the thread panel", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("message-timeline")).toContainText( @@ -484,7 +486,8 @@ test("message links to visible root messages open the thread panel", async ({ test("message links reopen a closed thread when the same messageId is already in the URL", async ({ page, }) => { - await page.goto( + await bootstrapE2ePage( + page, "/#/channels/9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50?messageId=mock-general-welcome", ); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -523,7 +526,8 @@ test("message links reopen a closed thread when the same messageId is already in }); test("message deep links survive reload", async ({ page }) => { - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${ENGINEERING_CHANNEL_ID}?messageId=mock-engineering-shipped`, ); @@ -555,7 +559,7 @@ test("cold-start channel deep link drains after the router mounts", async ({ ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("chat-title")).toHaveText("engineering"); await expect(page).toHaveURL( @@ -593,7 +597,7 @@ test("cold-start message deep link preserves its thread target", async ({ ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("chat-title")).toHaveText("watercooler"); await expect(page).toHaveURL(/messageId=mock-forum-release-reply/); diff --git a/desktop/tests/e2e/needs-restart-screenshots.spec.ts b/desktop/tests/e2e/needs-restart-screenshots.spec.ts index cc72d4211e..08a16475f8 100644 --- a/desktop/tests/e2e/needs-restart-screenshots.spec.ts +++ b/desktop/tests/e2e/needs-restart-screenshots.spec.ts @@ -11,7 +11,7 @@ * - Generic rendering: unknown field ids, number/array values, masked values. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -119,7 +119,7 @@ const START_AGENT = { }; async function gotoAgentsView(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("open-agents-view")).toBeVisible({ timeout: 10_000, }); diff --git a/desktop/tests/e2e/nostr-bind.spec.ts b/desktop/tests/e2e/nostr-bind.spec.ts index 0113eb0523..26ff38aea2 100644 --- a/desktop/tests/e2e/nostr-bind.spec.ts +++ b/desktop/tests/e2e/nostr-bind.spec.ts @@ -1,4 +1,10 @@ -import { expect, test, type Locator, type Page } from "../helpers/test"; +import { + expect, + test, + type Locator, + type Page, + bootstrapE2ePage, +} from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; type NostrBindPayload = { @@ -56,7 +62,7 @@ async function openNostrBind( mock?: Parameters[1], ) { await installMockBridge(page, mock); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof ( diff --git a/desktop/tests/e2e/observer-archive-policy.spec.ts b/desktop/tests/e2e/observer-archive-policy.spec.ts index 5349b13f2c..05cd6a3ce8 100644 --- a/desktop/tests/e2e/observer-archive-policy.spec.ts +++ b/desktop/tests/e2e/observer-archive-policy.spec.ts @@ -1,9 +1,9 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; async function openLocalArchiveSettings(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-settings").click(); await page.getByTestId("profile-popover-settings").click(); await expect(page.getByTestId("settings-view")).toBeVisible(); @@ -134,7 +134,7 @@ test.describe("observer archive policy — reconciliation gate", () => { ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); // Wait for the channel list (proves AppShell mounted fully). await expect(page.getByTestId("channel-general")).toBeVisible({ @@ -188,7 +188,7 @@ test.describe("observer archive policy — reconciliation gate", () => { saveSubscriptions: [], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("channel-general")).toBeVisible({ timeout: 10_000, }); diff --git a/desktop/tests/e2e/observer-feed-screenshots.spec.ts b/desktop/tests/e2e/observer-feed-screenshots.spec.ts index bfb7cd9e6c..993081dd16 100644 --- a/desktop/tests/e2e/observer-feed-screenshots.spec.ts +++ b/desktop/tests/e2e/observer-feed-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -34,7 +34,7 @@ async function openObserverFeedPanel( page: import("@playwright/test").Page, agentPubkey: string, ) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForSeedHook(page); await page.getByTestId("channel-agents").click(); diff --git a/desktop/tests/e2e/onboarding-agent-defaults.spec.ts b/desktop/tests/e2e/onboarding-agent-defaults.spec.ts index fbb1a048d7..ff3f77174a 100644 --- a/desktop/tests/e2e/onboarding-agent-defaults.spec.ts +++ b/desktop/tests/e2e/onboarding-agent-defaults.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { passThroughBackupStep } from "../helpers/onboarding"; @@ -88,7 +88,7 @@ test("setup shows all bundled harnesses as detected", async ({ page }) => { }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await expect(page.getByTestId("onboarding-runtime-claude")).toBeVisible(); @@ -134,7 +134,7 @@ test("setup distinguishes a missing CLI from an installed desktop app", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const card = page.getByTestId("onboarding-runtime-codex"); @@ -157,7 +157,7 @@ test("ready state is detected and enables Next without persisting a default", as }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await expect(page.getByTestId("onboarding-runtime-ready-claude")).toHaveText( @@ -186,7 +186,7 @@ test("setup shows runtime discovery loading before rendering harnesses", async ( }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await expect(page.getByTestId("onboarding-runtime-loading")).toBeVisible(); @@ -202,7 +202,7 @@ test("unknown authentication can be checked again", async ({ page }) => { { acpRuntimesCatalogSequence: [[unknown], [loggedIn]] }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const checkAgain = page.getByRole("button", { @@ -228,7 +228,7 @@ test("auth discovery failure stays actionable without exposing internals", async }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const card = page.getByTestId("onboarding-runtime-claude"); @@ -264,7 +264,7 @@ test("terminal launch failure keeps Sign in available", async ({ page }) => { }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const card = page.getByTestId("onboarding-runtime-claude"); @@ -301,7 +301,7 @@ test("sign in stays pending until catalog detection confirms Ready", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const signIn = page.getByRole("button", { name: "Sign in to Claude Code" }); @@ -361,7 +361,7 @@ test("failed install can be retried without shifting card content", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const card = page.getByTestId("onboarding-runtime-claude"); @@ -411,7 +411,7 @@ test("install transitions through Sign in to Ready", async ({ page }) => { }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const install = page.getByTestId("onboarding-runtime-install-claude"); @@ -447,7 +447,7 @@ test("defaults waits for baked configuration before rendering fields", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -475,7 +475,7 @@ test("defaults renders only fields supported by the selected harness", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -513,7 +513,7 @@ test("defaults hides model when optional harness has empty discovery", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -546,7 +546,7 @@ test("defaults keeps model control when optional harness discovery fails", async }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -580,7 +580,7 @@ test("defaults can be skipped while loading without persisting configuration", a }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -609,7 +609,7 @@ test("defaults stages auto-selection and edits without writing when skipped", as }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -657,7 +657,7 @@ test("Back preserves incomplete defaults draft without writing", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect( @@ -720,7 +720,7 @@ test("defaults auto-selects the only ready visible harness", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect(page.getByTestId("onboarding-page-config")).toBeVisible(); @@ -750,7 +750,7 @@ test("Next persists the latest staged harness choice", async ({ page }) => { }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -787,7 +787,7 @@ test("Next shows saving state and advances only after persistence", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); @@ -825,7 +825,7 @@ test("Next keeps the draft and retries after a save failure", async ({ }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect(page.getByTestId("global-agent-default-harness")).toHaveText( @@ -869,7 +869,7 @@ test("defaults requires a choice when multiple visible harnesses are ready", asy }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect(page.getByTestId("onboarding-page-config")).toBeVisible(); @@ -990,7 +990,7 @@ test("concurrent installs each keep their own state — one fails, one succeeds" }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); const claudeInstall = page.getByTestId("onboarding-runtime-install-claude"); @@ -1085,7 +1085,7 @@ test("Finish stays disabled until a provider-required harness is fully configure }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect(page.getByTestId("onboarding-page-config")).toBeVisible(); @@ -1136,7 +1136,7 @@ test("baked build config keeps Finish enabled without manual provider setup", as }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await navigateToSetupPage(page); await page.getByTestId("onboarding-setup-next").click(); await expect(page.getByTestId("onboarding-page-config")).toBeVisible(); diff --git a/desktop/tests/e2e/onboarding-avatar-skip.spec.ts b/desktop/tests/e2e/onboarding-avatar-skip.spec.ts index 9feba24022..4ba8732122 100644 --- a/desktop/tests/e2e/onboarding-avatar-skip.spec.ts +++ b/desktop/tests/e2e/onboarding-avatar-skip.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -17,7 +17,7 @@ test("avatar step always shows Skip for now button without an error", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -44,7 +44,7 @@ test("avatar step shares the profile emoji picker controls", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -80,7 +80,7 @@ test("avatar step skip button completes community profile setup", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -96,7 +96,7 @@ test("avatar Next button still requires an avatar to be chosen", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -120,7 +120,7 @@ test("avatar Next button still requires an avatar to be chosen", async ({ test("normal profile setup keeps the existing identity", async ({ page }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-page-1")).toBeVisible(); await expect(page.getByTestId("onboarding-import-key")).toHaveCount(0); @@ -137,7 +137,7 @@ test("Back from the community avatar step returns to profile", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); diff --git a/desktop/tests/e2e/onboarding-backup.spec.ts b/desktop/tests/e2e/onboarding-backup.spec.ts index 4e76b1c60c..46d2b28acf 100644 --- a/desktop/tests/e2e/onboarding-backup.spec.ts +++ b/desktop/tests/e2e/onboarding-backup.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; import { @@ -12,7 +12,7 @@ async function enterMachineBackup(page: import("@playwright/test").Page) { skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Create a new identity key" }).click(); } @@ -358,7 +358,7 @@ test("reveal shows inline error when get_nsec fails and Next still advances", as { nsecError: "Keychain locked" }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Create a new identity key" }).click(); await expect(page.getByTestId("onboarding-page-backup")).toBeVisible(); @@ -379,7 +379,7 @@ test("reveal retry succeeds after initial failure", async ({ page }) => { { nsecErrors: ["Keychain locked", null] }, { skipCommunitySeed: true, skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Create a new identity key" }).click(); await page.getByTestId("backup-key-reveal-toggle").click(); diff --git a/desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts b/desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts index 1ccc60cd78..bcf1ee65cf 100644 --- a/desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts +++ b/desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -22,7 +22,7 @@ test("machine onboarding: landing, backup, setup docked CTAs", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const gate = page.getByTestId("machine-onboarding-gate"); await expect(gate).toBeVisible(); @@ -141,7 +141,7 @@ test("machine key import remains usable in a short viewport", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); const heading = page.getByRole("heading", { name: "Enter your private key" }); @@ -183,7 +183,7 @@ test("backup options keep one-column geometry on narrow windows", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Create a new identity key" }).click(); await expect( page.getByRole("heading", { @@ -212,7 +212,7 @@ test("backup options keep one-column geometry on narrow windows", async ({ test("relay onboarding: profile and avatar docked CTAs", async ({ page }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-page-1")).toBeVisible(); await page.getByTestId("onboarding-display-name").fill("Ada Lovelace"); diff --git a/desktop/tests/e2e/onboarding.spec.ts b/desktop/tests/e2e/onboarding.spec.ts index b748ee5237..fa536e31ad 100644 --- a/desktop/tests/e2e/onboarding.spec.ts +++ b/desktop/tests/e2e/onboarding.spec.ts @@ -1,5 +1,5 @@ import { hexToBytes } from "@noble/hashes/utils.js"; -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { nsecEncode } from "nostr-tools/nip19"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -690,7 +690,7 @@ test("completed users skip the loading gate while profile is still settling", as await installMockBridge(page, { profileReadDelayMs: 3_000, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); await expectHomeView(page); @@ -703,7 +703,7 @@ test("fresh existing-identity path leads with private-key recovery", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); await expect( @@ -794,7 +794,7 @@ test("first-launch key import continues to machine setup", async ({ page }) => { skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); const importedNsec = nsecEncode(hexToBytes(TEST_IDENTITIES.alice.privateKey)); @@ -817,7 +817,7 @@ test("key import locks host navigation and ignores rapid duplicate submits", asy skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); const importedNsec = nsecEncode(hexToBytes(TEST_IDENTITIES.alice.privateKey)); @@ -841,7 +841,7 @@ test("imported-key users can skip out of harness setup", async ({ page }) => { skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); const importedNsec = nsecEncode(hexToBytes(TEST_IDENTITIES.alice.privateKey)); @@ -865,7 +865,7 @@ test("first-launch encrypted backup import asks for a passphrase and continues", skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); // Spec-vector blob the mock bridge accepts with the mock passphrase. @@ -919,7 +919,7 @@ test("first-launch import accepts an .ncryptsec backup file", async ({ skipCommunitySeed: true, skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: "Use an existing key" }).click(); @@ -1042,7 +1042,7 @@ test("non-local runtime override keeps community selection without release flag" skipOnboardingSeed: true, skipCommunitySeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("button", { name: /Join a community/ }), @@ -1068,7 +1068,7 @@ test("non-local default auto-connects when the release flag is enabled", async ( skipOnboardingSeed: true, skipCommunitySeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectIncompleteOnboarding(page); await expect @@ -1108,7 +1108,7 @@ test("first-community choices route join, create, owner, and member intents", as skipOnboardingSeed: true, skipCommunitySeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("button", { name: /Join a community/ }), @@ -1195,7 +1195,7 @@ test("first-community owner can connect an existing hosted community", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await expect(page.getByText("North Star")).toBeVisible(); @@ -1253,7 +1253,7 @@ test("first-community owner can create and connect a hosted community", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page.getByRole("button", { name: "Sign in to continue" }).click(); @@ -1329,7 +1329,7 @@ test("hosted community address line stays within the card for a long name", asyn ); // The 800px app minimum is the worst case for the full-width address line. await page.setViewportSize({ width: 800, height: 720 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page.getByRole("button", { name: "Sign in to continue" }).click(); @@ -1398,7 +1398,7 @@ test("first-community reports a created community without a relay address", asyn skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page.getByRole("textbox", { name: "Community name" }).fill("bee-lab"); @@ -1429,7 +1429,7 @@ test("first-community X cancels a pending sign-in", async ({ page }) => { skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page.getByRole("button", { name: "Sign in to continue" }).click(); @@ -1471,7 +1471,7 @@ test("first-community owner can replace a mismatched account identity", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await expect( @@ -1521,7 +1521,7 @@ test("first-community explains when the local identity belongs to another accoun skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page @@ -1562,7 +1562,7 @@ test("back clears Builderlab auth before returning to first-community choices", skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-choice-create").click(); await page.getByRole("button", { name: "Back" }).click(); @@ -1609,7 +1609,7 @@ test("first-community shows the scenario cards for localhost", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect( page.getByRole("button", { name: "Join default community" }), @@ -1669,7 +1669,7 @@ test("first-community direct join reaches profile", async ({ page }) => { skipOnboardingSeed: true, skipCommunitySeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: /Join a community/ }).click(); await page @@ -1744,7 +1744,7 @@ test("community onboarding reuses an existing relay profile", async ({ skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect .poll(() => @@ -1799,7 +1799,7 @@ test("first-community direct join cancel returns to request access", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByRole("button", { name: /Join a community/ }).click(); await page @@ -1874,7 +1874,7 @@ test("canceling a join to an existing inactive community preserves it", async ({ skipCommunitySeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate((transactionStorageKey) => { const timestamp = new Date().toISOString(); @@ -1977,7 +1977,7 @@ test("connected first-community profile keeps Back bottom-left and balances the skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("community-onboarding-flow")).toBeVisible(); await expect( @@ -2357,7 +2357,7 @@ test("name-only community profile save preserves an existing avatar", async ({ relayWsUrl: "wss://default.example.com", skipOnboardingSeed: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const existingAvatarUrl = "https://mock.relay/media/existing-community-avatar.png"; @@ -2424,7 +2424,7 @@ test("pending avatar stays navigable, clears failures, and retries", async ({ skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-profile-name-key").fill("Tyler"); await uploadCommunityAvatar(page, "pending-community-avatar.png"); @@ -2550,7 +2550,7 @@ test("a pending avatar never becomes durable if propagation fails after onboardi skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-profile-name-key").fill("Tyler"); await uploadCommunityAvatar(page, "saved-pending-community-avatar.png"); @@ -2622,7 +2622,7 @@ test("a pending avatar becomes durable after onboarding unmounts once ready", as skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-profile-name-key").fill("Tyler"); await uploadCommunityAvatar(page, "ready-after-unmount-community-avatar.png"); @@ -2696,7 +2696,7 @@ test("a failed pending replacement leaves the confirmed avatar untouched", async skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await seedCurrentAvatar(page, existingAvatarUrl); await page.getByTestId("community-profile-name-key").fill("Tyler"); @@ -2764,7 +2764,7 @@ test("replacing a pending upload disposes its verifier and local preview", async skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-profile-name-key").fill("Tyler"); await uploadCommunityAvatar(page, "superseded-community-avatar.png"); @@ -2836,7 +2836,7 @@ test("membership denial on community profile save offers recovery", async ({ skipOnboardingSeed: true, }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("community-profile-name-key").fill("Kalvin"); await page.getByTestId("community-profile-next").click(); @@ -2868,7 +2868,7 @@ test("identity fallback text does not count as a real onboarding name", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectIncompleteOnboarding(page); await expect(page.getByTestId("onboarding-next")).toBeDisabled(); @@ -2885,7 +2885,7 @@ test("first-run blank identity with no profile event sees onboarding", async ({ // Do NOT seed searchProfiles for tyler's pubkey, so ensureMockProfile // constructs a synthesised profile with has_profile_event: false. await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectIncompleteOnboarding(page); }); @@ -2912,7 +2912,7 @@ test("returning user with blank display name and real profile event skips onboar }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Profile event exists → onboarding is skipped, app renders. await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); @@ -2950,7 +2950,7 @@ test("no-event profile cached then reloaded still sees onboarding", async ({ ); // No profile event on the relay either — ensureMockProfile uses false. await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Cache seed must NOT promote hasProfileEvent to true. Onboarding shows. await expectIncompleteOnboarding(page); @@ -2961,7 +2961,7 @@ test("avatar step uses an add-image placeholder before an avatar is chosen", asy }) => { await seedActiveIdentity(page, BLANK_AVATAR_PLACEHOLDER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -2978,7 +2978,7 @@ test("avatar step reveals preset backgrounds after the first emoji pick", async }) => { await seedActiveIdentity(page, BLANK_AVATAR_EMOJI_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3004,7 +3004,7 @@ test("avatar step accepts an avatar URL before completing onboarding", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3029,7 +3029,7 @@ test("failed avatar saves can continue without saving the avatar", async ({ }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, {}, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3084,7 +3084,7 @@ test("avatar upload rejects a file whose server-detected MIME is not an image", }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3121,7 +3121,7 @@ test("avatar upload accepts a file whose server-detected MIME is an image", asyn }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3144,7 +3144,7 @@ test("first-run onboarding keeps the shell hidden and lands on private Welcome a }) => { await seedActiveIdentity(page, FIRST_RUN_ALICE); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toBeVisible(); await expect(page.getByTestId("onboarding-page-1")).toBeVisible(); @@ -3178,7 +3178,7 @@ test("failed public starter channel setup does not show a retry toast", async ({ { ensureStarterChannelsErrors: [starterError, starterError] }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3205,7 +3205,7 @@ test("first-run onboarding posts the live Fizz kickoff", async ({ page }) => { }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3230,7 +3230,7 @@ test("first-run onboarding lands before Welcome team bootstrap completes", async { createManagedAgentDelayMs: 1_000 }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3264,7 +3264,7 @@ test("existing relay profile with display name auto-skips onboarding without loc }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); await expectHomeView(page); @@ -3277,7 +3277,7 @@ test("onboarding uses the existing identity when the community is already set up // this identity. Profile setup must not offer to create or replace it. await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-display-name")).toHaveValue(""); await expect(page.getByTestId("onboarding-next")).toHaveText("Continue"); @@ -3291,7 +3291,7 @@ test("completed onboarding backfills missing starter channels", async ({ await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await seedOnboardingCompletion(page, BLANK_TYLER_IDENTITY.pubkey); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); await expectHomeView(page); @@ -3306,7 +3306,7 @@ test("finishing onboarding creates starter channels and focuses welcome-everyone }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3323,7 +3323,7 @@ test("welcome-everywhere banner: X dismiss removes the guidance surface", async }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3349,7 +3349,7 @@ test("welcome-everywhere banner: dismiss persists after channel re-entry", async }) => { await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge(page, undefined, { skipOnboardingSeed: true }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await completeProfileOnboarding(page); @@ -3384,7 +3384,7 @@ test("initial profile read failures still hold incomplete users in onboarding", }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectIncompleteOnboarding(page); }); @@ -3400,7 +3400,7 @@ test("failed first profile saves can be skipped for the current session", async }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toBeVisible(); await expect(page.getByTestId("onboarding-display-name")).toHaveValue(""); @@ -3426,7 +3426,7 @@ test("generic relay save failures use the generic reconnect card", async ({ }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3454,7 +3454,7 @@ test("custom relay proxy sign-in failures use the generic reconnect card", async }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3478,7 +3478,7 @@ test("community access failures use the generic reconnect card", async ({ }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3501,7 +3501,7 @@ test("dismissed relay save failures reappear on retry", async ({ page }) => { }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3542,7 +3542,7 @@ test("existing relay profile with display name auto-completes onboarding", async }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("onboarding-gate")).toHaveCount(0); await expectHomeView(page); @@ -3560,7 +3560,7 @@ test("open relay skips membership gating during onboarding", async ({ }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3581,7 +3581,7 @@ test("membership denial can import a different invited key", async ({ }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3640,7 +3640,7 @@ test("onboarding relay reconnect — click shows Connected then auto-dismisses", }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3685,7 +3685,7 @@ test("onboarding relay reconnect — connected without a prior click does not sh }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("onboarding-display-name").fill("Morty QA"); await page.getByTestId("onboarding-next").click(); @@ -3720,7 +3720,7 @@ test("membership denied shows all four affordances and change-community edits no }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Fill the display name and advance — membership check triggers denial. await page.getByTestId("onboarding-display-name").fill("Morty QA"); @@ -3796,7 +3796,7 @@ test("cancel from profile Back preserves drafts and denied Back returns to inter }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // --- Profile step: Back opens community-change overlay --- const nameInput = page.getByTestId("onboarding-display-name"); @@ -3840,7 +3840,7 @@ test("denied on relay A then paste relay B invite URL switches community to B", }, { skipOnboardingSeed: true }, ); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Record the initial relay URL (relay A). const initialRelayUrl = await page.evaluate(() => { diff --git a/desktop/tests/e2e/overscroll-boundary.spec.ts b/desktop/tests/e2e/overscroll-boundary.spec.ts index c1efbeeab8..e8fa0bb99b 100644 --- a/desktop/tests/e2e/overscroll-boundary.spec.ts +++ b/desktop/tests/e2e/overscroll-boundary.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -34,7 +34,7 @@ test.beforeEach(async ({ page }) => { test("locks viewport rubber-band outside conversation scrollers", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("message-timeline")).toBeVisible(); @@ -82,7 +82,7 @@ test("locks viewport rubber-band outside conversation scrollers", async ({ }); test("locks horizontal viewport pan everywhere", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("message-timeline")).toBeVisible(); diff --git a/desktop/tests/e2e/parity-ancestor-island.spec.ts b/desktop/tests/e2e/parity-ancestor-island.spec.ts index 5eb4719bdc..4e1ed1ade7 100644 --- a/desktop/tests/e2e/parity-ancestor-island.spec.ts +++ b/desktop/tests/e2e/parity-ancestor-island.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installRelayBridge } from "../helpers/bridge"; import { assertRelaySeeded } from "../helpers/seed"; @@ -66,7 +66,7 @@ test("live relay: an ancestor island does not strand the history frontier", asyn expect(expectedGap.size).toBe(GAP_COUNT); await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/persistent-agent-audience.spec.ts b/desktop/tests/e2e/persistent-agent-audience.spec.ts index 28f99ea68b..29c3bb9dd7 100644 --- a/desktop/tests/e2e/persistent-agent-audience.spec.ts +++ b/desktop/tests/e2e/persistent-agent-audience.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -26,14 +26,15 @@ async function seedAudience(page: Page, pubkeys: string[], theme = "buzz") { } async function openGeneral(page: Page) { - await page.goto(`/#/channels/${CHANNEL_ID}`, { + await bootstrapE2ePage(page, `/#/channels/${CHANNEL_ID}`, { waitUntil: "domcontentloaded", }); await expect(page.getByTestId("chat-title")).toHaveText("general"); } async function openThread(page: Page, threadRootId = THREAD_ROOT_ID) { - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${CHANNEL_ID}?messageId=${threadRootId}&thread=${threadRootId}`, { waitUntil: "domcontentloaded" }, ); diff --git a/desktop/tests/e2e/persona-env-vars.spec.ts b/desktop/tests/e2e/persona-env-vars.spec.ts index d336b03f6e..68c3d1ea2f 100644 --- a/desktop/tests/e2e/persona-env-vars.spec.ts +++ b/desktop/tests/e2e/persona-env-vars.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => { }); async function gotoApp(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); await expect(page.getByTestId("open-agents-view")).toBeVisible({ timeout: 10_000, diff --git a/desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts b/desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts index bb45c243d0..3709358ccd 100644 --- a/desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts +++ b/desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -27,7 +27,7 @@ async function waitForInvokeBridge(page: import("@playwright/test").Page) { * Returns the dialog locator. */ async function openNewPersonaDialog(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); await page.getByTestId("open-agents-view").click(); diff --git a/desktop/tests/e2e/persona-sync.spec.ts b/desktop/tests/e2e/persona-sync.spec.ts index de96725e5a..cfaa2fc85c 100644 --- a/desktop/tests/e2e/persona-sync.spec.ts +++ b/desktop/tests/e2e/persona-sync.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { hexToBytes } from "@noble/hashes/utils.js"; import { finalizeEvent } from "nostr-tools/pure"; @@ -24,7 +24,7 @@ test.beforeEach(async ({ page }) => { }); async function gotoApp(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); await expect(page.getByTestId("open-agents-view")).toBeVisible({ timeout: 10_000, diff --git a/desktop/tests/e2e/profile-active-turn.spec.ts b/desktop/tests/e2e/profile-active-turn.spec.ts index 2abdb14776..400ae2aa0f 100644 --- a/desktop/tests/e2e/profile-active-turn.spec.ts +++ b/desktop/tests/e2e/profile-active-turn.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -38,7 +38,7 @@ async function waitForBridge(page: import("@playwright/test").Page) { } async function openAgentsChannel(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await waitForBridge(page); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); diff --git a/desktop/tests/e2e/profile-backup-settings.spec.ts b/desktop/tests/e2e/profile-backup-settings.spec.ts index 81a16016be..91b9f73f93 100644 --- a/desktop/tests/e2e/profile-backup-settings.spec.ts +++ b/desktop/tests/e2e/profile-backup-settings.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { npubEncode } from "nostr-tools/nip19"; import { installMockBridge } from "../helpers/bridge"; @@ -28,7 +28,7 @@ async function openBackupSettings( mock?: Parameters[1], ) { await installMockBridge(page, mock); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await openIdentity(page); } diff --git a/desktop/tests/e2e/profile-custom-emoji-status.spec.ts b/desktop/tests/e2e/profile-custom-emoji-status.spec.ts index b1a1c2b696..be7dbff9f1 100644 --- a/desktop/tests/e2e/profile-custom-emoji-status.spec.ts +++ b/desktop/tests/e2e/profile-custom-emoji-status.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -24,7 +24,7 @@ test.beforeEach(async ({ page }) => { test("profile popover renders a custom emoji status as an image", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openProfilePopover(page); await page.getByTestId("profile-popover-set-status").click(); diff --git a/desktop/tests/e2e/profile-nsec-reveal.spec.ts b/desktop/tests/e2e/profile-nsec-reveal.spec.ts index 9e3447f706..2291d871f0 100644 --- a/desktop/tests/e2e/profile-nsec-reveal.spec.ts +++ b/desktop/tests/e2e/profile-nsec-reveal.spec.ts @@ -2,7 +2,7 @@ * Compact E2E tests for NsecRevealRow in ProfileSettingsCard. * Covers: reveal fetches + renders masked value, error state, collapse clears state. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -22,7 +22,7 @@ test("reveal shows masked nsec value and hides it again on collapse", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await expandIdentity(page); @@ -49,7 +49,7 @@ test("reveal shows masked nsec value and hides it again on collapse", async ({ test("reveal shows error when get_nsec fails", async ({ page }) => { await installMockBridge(page, { nsecError: "Keychain locked" }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await expandIdentity(page); diff --git a/desktop/tests/e2e/profile.spec.ts b/desktop/tests/e2e/profile.spec.ts index 354c1a7667..29d2f7fec2 100644 --- a/desktop/tests/e2e/profile.spec.ts +++ b/desktop/tests/e2e/profile.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { createMockAgentMemoryListing, @@ -276,7 +276,7 @@ test.beforeEach(async ({ page }) => { test("profile panel shows communication actions as quick action tiles", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -346,7 +346,7 @@ test("owned agent profile stays in parity between Agents and its DM", async ({ agentMemory: createMockAgentMemoryListing(), oaOwnerIsMe: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const agentName = "Parity Bot"; const agentPubkey = await addGenericAgent( page, @@ -437,7 +437,7 @@ test("keeps the saved profile description after a community round trip", async ( window.localStorage.setItem("buzz-communities", JSON.stringify(seed)); window.localStorage.setItem("buzz-active-community-id", seed[0].id); }, communities); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const description = "Description that should survive switching"; await openSettings(page, "profile"); @@ -467,7 +467,7 @@ test("updates the relay-backed profile from settings", async ({ page }) => { const displayName = `Tyler QA ${stamp}`; const avatarUrl = `https://example.com/avatar-${stamp}.png`; const about = `Coordinating relay profile setup ${stamp}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await expect( @@ -524,7 +524,7 @@ test("updates the relay-backed profile from settings", async ({ page }) => { }); test("saves profile metadata from the block Done button", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await expect(page.getByTestId("profile-display-name-value")).toHaveText( @@ -578,7 +578,7 @@ test("saves profile metadata from the block Done button", async ({ page }) => { }); test("shows profile save feedback as a toast", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-metadata-edit").click(); @@ -601,7 +601,7 @@ test("nests the avatar edit button in a clipped notch", async ({ page }) => { await page.addInitScript(() => { window.localStorage.setItem("buzz-theme", "github-light"); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); @@ -632,7 +632,7 @@ test("nests the avatar edit button in a clipped notch", async ({ page }) => { test("swaps the avatar preview and mode tabs while editing", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); @@ -677,7 +677,7 @@ test("swaps the avatar preview and mode tabs while editing", async ({ test("highlights the avatar drop target while dragging an image", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -763,7 +763,7 @@ test("uploads local profile avatar files before saving", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -814,7 +814,7 @@ test("uploads local profile avatar files before saving", async ({ page }) => { test("renders emoji avatars with a static background layer", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -840,7 +840,7 @@ test("renders emoji avatars with a static background layer", async ({ test("offers emoji search and skin-tone controls for profile avatars", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -913,7 +913,7 @@ test("reveals emoji background colors only after choosing an emoji", async ({ page, }) => { const imageAvatarUrl = `https://example.com/avatar-color-controls-${Date.now()}.png`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -954,7 +954,7 @@ test("reveals emoji background colors only after choosing an emoji", async ({ }); test("snaps custom avatar colors to the dot grid", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); await page.getByTestId("profile-avatar-edit").click(); @@ -1001,7 +1001,7 @@ test("snaps custom avatar colors to the dot grid", async ({ page }) => { }); test("opens Send feedback from the profile menu", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openProfileMenu(page); await page.getByTestId("profile-popover-send-feedback").click(); await expect(page.getByTestId("send-feedback-dialog")).toBeVisible(); @@ -1025,7 +1025,7 @@ test("keeps Send disabled when a stale attachment attempt finishes", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openProfileMenu(page); await page.getByTestId("profile-popover-send-feedback").click(); @@ -1068,7 +1068,7 @@ test("proxies feedback attachment previews", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openProfileMenu(page); await page.getByTestId("profile-popover-send-feedback").click(); @@ -1084,7 +1084,7 @@ test("proxies feedback attachment previews", async ({ page }) => { }); test("updates presence from the profile menu", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openProfileMenu(page); await expect( @@ -1116,7 +1116,7 @@ test("renders agent profile ingress subviews from the Playwright mock bridge", a oaOwnerIsMe: true, }); await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const longAgentInstruction = [ "Watch the channel and help when asked.", @@ -1868,7 +1868,7 @@ test("an older agent message opens the same persona instance as the Agents libra }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId(`persona-agent-row-${personaId}`).click(); @@ -1921,7 +1921,7 @@ test("restored Inbox deep link hides the back arrow", async ({ page }) => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -1966,7 +1966,7 @@ test("declared owner sees runtime tab for a remote relay agent", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -2003,7 +2003,7 @@ test("declared owner sees runtime tab for a remote relay agent", async ({ test("declared owner sees runtime tab without a relay-agent record", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -2048,7 +2048,7 @@ test("declared owner sees runtime tab without a relay-agent record", async ({ test("non-owner agent profile shows only reported public agent data", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-agents").click(); await expect(page.getByTestId("chat-title")).toHaveText("agents"); @@ -2093,7 +2093,7 @@ test("owned agent absent from relay/managed lists still renders agent framing", { pubkey: ednaPubkey, displayName: "Edna", isAgent: true }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -2133,7 +2133,7 @@ test("owned agent absent from relay/managed lists still renders agent framing", test("renders settings in the app shell with a back button", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const inboxNavButton = page .getByTestId("app-sidebar") @@ -2194,7 +2194,7 @@ test("notification settings drive the Inbox badge and desktop alerts", async ({ }); } - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("sidebar-home-count")).toHaveCount(0); await openSettings(page, "notifications"); @@ -2322,7 +2322,7 @@ test("notification settings drive the Inbox badge and desktop alerts", async ({ test("desktop notification clicks open the matching forum thread", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "notifications"); await expect(page.getByTestId("notifications-desktop-state")).toContainText( @@ -2396,7 +2396,7 @@ test("desktop notification clicks open the matching forum thread", async ({ test("opens settings with the keyboard shortcut and updates theme", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectHomeView(page); await page.keyboard.press( @@ -2484,7 +2484,7 @@ test("opens settings with the keyboard shortcut and updates theme", async ({ }); test("supports webview zoom keyboard shortcuts", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectHomeView(page); const getTextScaleState = () => @@ -2641,7 +2641,7 @@ test("storage clear resets composed font size and keyboard zoom across windows", }); test("shows agent runtimes in agent settings", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "agents"); @@ -2698,7 +2698,7 @@ test("shows agent runtimes in agent settings", async ({ page }) => { test("settings subtitles share the Appearance secondary color", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "appearance"); const appearancePanel = page.getByTestId("settings-panel-appearance"); diff --git a/desktop/tests/e2e/project-commit-detail.spec.ts b/desktop/tests/e2e/project-commit-detail.spec.ts index 860d10a5f5..2edb67d3bd 100644 --- a/desktop/tests/e2e/project-commit-detail.spec.ts +++ b/desktop/tests/e2e/project-commit-detail.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -25,7 +25,7 @@ test("top-level project lists align dates and overflow actions", async ({ window.localStorage.setItem("buzz.projects.viewMode", "list"); }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await expect( page.getByRole("heading", { level: 1, name: "Projects" }), @@ -218,7 +218,7 @@ test("creating a project publishes its initial repository grouping", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-create-menu").hover(); await page.getByRole("menuitem", { name: "Project" }).click(); @@ -288,7 +288,7 @@ test("unsupported relays keep the initial repository accessible", async ({ window.__BUZZ_E2E_UNSUPPORTED_PROJECT_ANNOUNCEMENTS__ = true; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-create-menu").hover(); await page.getByRole("menuitem", { name: "Project" }).click(); @@ -336,7 +336,7 @@ test("project creation can retry after its repository publication fails", async window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__ = [30621]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-create-menu").hover(); await page.getByRole("menuitem", { name: "Project" }).click(); @@ -365,7 +365,7 @@ test("project creation is idempotent after a lost publish acknowledgement", asyn window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__ = [30621]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-create-menu").hover(); await page.getByRole("menuitem", { name: "Project" }).click(); @@ -405,7 +405,7 @@ test("multi-repository projects switch the active repository", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); await page @@ -493,7 +493,7 @@ test("commit detail opens from the commits feed with a diff", async ({ await installMockBridge(page); // The preview server is a static file server without SPA fallback, so // enter at "/" and navigate via the sidebar. - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); // The overview no longer lists repository cards — switch to the @@ -604,7 +604,7 @@ test("pull request and issue feeds share the commit row structure", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); // The overview no longer lists repository cards — switch to the @@ -667,7 +667,7 @@ test("adding a repository retries and reports an error when the 30617 publicatio window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__ = [30617, 30617]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); await page @@ -734,7 +734,7 @@ test("adding a repository treats a lost 30617 acknowledgement as success", async window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__ = [30617]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); await page @@ -802,7 +802,7 @@ test("adding a repository blocks when a standalone 30617 already exists at that { owner: MOCK_OWNER, dtag: STANDALONE_DTAG }, ); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); await page @@ -895,7 +895,8 @@ test("navigating via a 30617 entity-link route opens the correct non-primary rep // TanStack Router's param extractor receives the raw decoded segment, and // %3A would be passed through literally (as the string "30617%3A…") rather // than decoded to "30617:…", causing the project lookup to fail. - await page.goto( + await bootstrapE2ePage( + page, `/#/projects/${RELAY_TOOLS_ADDRESS}?pullRequestId=${KNOWN_PR_ID}`, { waitUntil: "domcontentloaded" }, ); diff --git a/desktop/tests/e2e/project-inbox.spec.ts b/desktop/tests/e2e/project-inbox.spec.ts index cfde34f45d..ed21f1f0b9 100644 --- a/desktop/tests/e2e/project-inbox.spec.ts +++ b/desktop/tests/e2e/project-inbox.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -18,7 +18,7 @@ test("Buzz Git pull request renders and stays actionable in Inbox", async ({ await installMockBridge(page); await page.setViewportSize({ width: 1024, height: 720 }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByRole("button", { name: "Repositories", exact: true }).click(); await page diff --git a/desktop/tests/e2e/project-issue-comments.spec.ts b/desktop/tests/e2e/project-issue-comments.spec.ts index 47c7373e7e..78791ef410 100644 --- a/desktop/tests/e2e/project-issue-comments.spec.ts +++ b/desktop/tests/e2e/project-issue-comments.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -10,7 +10,7 @@ const ISSUE_COMMENTS = [ ]; async function openBuzzProject(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); const projectEntry = page diff --git a/desktop/tests/e2e/project-pr-review.spec.ts b/desktop/tests/e2e/project-pr-review.spec.ts index 73cc1dcbbd..fe1ec9da88 100644 --- a/desktop/tests/e2e/project-pr-review.spec.ts +++ b/desktop/tests/e2e/project-pr-review.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -20,7 +20,7 @@ async function enableProjectsFeature(page: import("@playwright/test").Page) { } async function openBuzzProject(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); const projectEntry = page @@ -777,7 +777,7 @@ test("project pull requests preserve partial results from batched queries", asyn window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1619]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page .getByRole("button", { name: "Pull Requests", exact: true }) @@ -832,7 +832,7 @@ test("project pull request author rollover stays identity-only", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page .getByRole("button", { name: "Pull Requests", exact: true }) @@ -865,7 +865,7 @@ test("project issue author rollover matches pull requests", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByRole("button", { name: "Issues", exact: true }).click(); await page.getByRole("button", { name: "List layout" }).click(); @@ -899,7 +899,7 @@ test("project pull requests report aggregate root query failures", async ({ window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1618]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page .getByRole("button", { name: "Pull Requests", exact: true }) @@ -927,7 +927,7 @@ test("project issues preserve partial results from aggregate queries", async ({ window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByRole("button", { name: "Issues", exact: true }).click(); @@ -959,7 +959,7 @@ test("project overview reports aggregate work-item failures", async ({ window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1618]; }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await expect( @@ -981,7 +981,7 @@ test("project overview does not paint a background behind its cards", async ({ }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await expect(page.getByTestId("projects-overview-panel")).toHaveCSS( @@ -1014,7 +1014,7 @@ test("project overview does not paint a background behind its cards", async ({ test("repository rows identify their git host", async ({ page }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByRole("button", { name: "Repositories", exact: true }).click(); await page.getByRole("button", { name: "List layout" }).click(); @@ -1043,7 +1043,7 @@ test("project subsections do not paint backgrounds behind list or grid items", a }) => { await enableProjectsFeature(page); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); for (const section of ["Repositories", "Pull Requests", "Issues"] as const) { diff --git a/desktop/tests/e2e/projects-v3-screenshots.spec.ts b/desktop/tests/e2e/projects-v3-screenshots.spec.ts index 7b49b32163..aa058ef0b1 100644 --- a/desktop/tests/e2e/projects-v3-screenshots.spec.ts +++ b/desktop/tests/e2e/projects-v3-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -6,7 +6,7 @@ import { installMockBridge } from "../helpers/bridge"; const SHOTS = "test-results/projects-v3-screenshots"; async function openBuzzProject(page: import("@playwright/test").Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-projects").click(); const projectEntry = page @@ -202,7 +202,7 @@ test("projects v3 work-item list metadata", async ({ page }) => { window.localStorage.setItem("buzz.projects.viewMode", "list"); }); await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await page.getByTestId("projects-section-prs").click(); diff --git a/desktop/tests/e2e/pubkey-display-screenshots.spec.ts b/desktop/tests/e2e/pubkey-display-screenshots.spec.ts index 8186e26812..ca81385d97 100644 --- a/desktop/tests/e2e/pubkey-display-screenshots.spec.ts +++ b/desktop/tests/e2e/pubkey-display-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, @@ -18,7 +18,7 @@ test("profile panel Public key row reveals its copy action on hover", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -62,7 +62,7 @@ test("new-DM agent name swaps to its public key on name hover", async ({ }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); @@ -135,7 +135,7 @@ test("selected new-DM recipient can be verified again through search", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openNewMessagePage(page); await expect(page.getByTestId("new-message-page")).toBeVisible(); @@ -230,7 +230,7 @@ test("selected new-DM recipient can be verified again through search", async ({ test("member removal confirm shows the full npub inline", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/reaction-names.spec.ts b/desktop/tests/e2e/reaction-names.spec.ts index 89ab08e2ef..edede2a949 100644 --- a/desktop/tests/e2e/reaction-names.spec.ts +++ b/desktop/tests/e2e/reaction-names.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import * as fs from "node:fs"; import * as path from "node:path"; @@ -70,7 +70,7 @@ test.beforeEach(async ({ page }) => { test("reaction popover resolves a reactor with no authored message in the window", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.waitForFunction( @@ -118,7 +118,7 @@ test("reaction popover resolves a reactor with no authored message in the window test("maximum-length reaction name wraps inside a fixed-width popover", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.waitForFunction( diff --git a/desktop/tests/e2e/reaction-order.spec.ts b/desktop/tests/e2e/reaction-order.spec.ts index b2b9455411..7bc0d1660a 100644 --- a/desktop/tests/e2e/reaction-order.spec.ts +++ b/desktop/tests/e2e/reaction-order.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import * as fs from "node:fs"; import * as path from "node:path"; @@ -30,7 +30,7 @@ function reactionTargetRow(page: import("@playwright/test").Page) { } async function openGeneral(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); } diff --git a/desktop/tests/e2e/relay-connectivity.spec.ts b/desktop/tests/e2e/relay-connectivity.spec.ts index 031a16be6c..a910402d95 100644 --- a/desktop/tests/e2e/relay-connectivity.spec.ts +++ b/desktop/tests/e2e/relay-connectivity.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -77,7 +77,7 @@ async function driveConnectionDegraded( test.describe("relay connectivity", () => { test("01 — sidebar unreachable card", async ({ page }) => { await installMockBridge(page, { channelsReadError: RELAY_UNREACHABLE }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Wait for the E2E seam to be installed (bridge init is complete), then force // the relay into a degraded state. The card is gated on !isRelayConnectionConnected: @@ -113,7 +113,7 @@ test.describe("relay connectivity", () => { test("02 — sidebar reconnect card while reconnecting", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Wait for a healthy boot, then force the relay into "reconnecting" state. await expect(page.getByTestId("channel-general")).toBeVisible(); @@ -134,7 +134,7 @@ test.describe("relay connectivity", () => { test("03 — canvas unreachable in management sheet", async ({ page }) => { await installMockBridge(page, { canvasReadError: RELAY_UNREACHABLE }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -186,7 +186,7 @@ test.describe("relay connectivity", () => { }, ); await installMockBridge(page, { profileReadError: RELAY_UNREACHABLE }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // The profile card should show the cached display name. const profileCard = page.getByTestId("sidebar-profile-card"); @@ -197,7 +197,7 @@ test.describe("relay connectivity", () => { test("05 — no-cache npub fallback when offline", async ({ page }) => { // No cache seeded — profile card falls back to the mock identity npub name. await installMockBridge(page, { profileReadError: RELAY_UNREACHABLE }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const profileCard = page.getByTestId("sidebar-profile-card"); // Default mock identity display name is "npub1mock...". @@ -209,7 +209,7 @@ test.describe("relay connectivity", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await driveConnectionDegraded(page); diff --git a/desktop/tests/e2e/relay-reconnect-affordance.spec.ts b/desktop/tests/e2e/relay-reconnect-affordance.spec.ts index a0024e646d..4b6ab1b095 100644 --- a/desktop/tests/e2e/relay-reconnect-affordance.spec.ts +++ b/desktop/tests/e2e/relay-reconnect-affordance.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -52,7 +52,7 @@ test.describe("relay reconnect affordance", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await expect(page.getByTestId("sidebar-relay-unreachable")).toHaveCount(0); @@ -71,7 +71,7 @@ test.describe("relay reconnect affordance", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await driveConnectionState(page, "stalled"); diff --git a/desktop/tests/e2e/relay-reconnect.spec.ts b/desktop/tests/e2e/relay-reconnect.spec.ts index 4dd53166e3..b1d26e0bfd 100644 --- a/desktop/tests/e2e/relay-reconnect.spec.ts +++ b/desktop/tests/e2e/relay-reconnect.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -215,7 +215,7 @@ test("failed initial relay dial retries automatically", async ({ page }) => { await installMockBridge(page, { websocketConnectErrors: ["mock relay pod unavailable"], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // App-shell preconnect owns a keep-alive request. The first native dial is // rejected before a socket ID exists; the session must still enter its @@ -241,7 +241,7 @@ test("failed initial relay dial retries automatically", async ({ page }) => { test("routine traffic cannot bypass outage backoff and recovery stays automatic", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await setMockWebsocketUnavailable(page, true); @@ -290,7 +290,7 @@ test("routine traffic cannot bypass outage backoff and recovery stays automatic" test("authenticated reconnect reports connected while replay is rate-limited", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await activateRelayRateLimit(page, 5); @@ -312,7 +312,7 @@ test("authenticated reconnect reports connected while replay is rate-limited", a test("rate-limited reconnect backfill does not tear down the authenticated socket", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -368,7 +368,7 @@ test("service restart close resets accumulated backoff", async ({ page }) => { await installMockBridge(page, { websocketConnectErrors: ["down 1", "down 2", "down 3"], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible({ timeout: 15_000, }); @@ -388,7 +388,7 @@ test("service restart close resets accumulated backoff", async ({ page }) => { test("passive relay watchdog does not write while the websocket is half-open", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.getByTestId("message-timeline")).toContainText( @@ -414,7 +414,7 @@ test("passive relay watchdog does not write while the websocket is half-open", a test("sidebar reconnect prompt flips on live relay degradation without a query error", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Healthy boot: channels render from the mock bridge and the reconnect // prompt is absent (no query error, connection healthy). @@ -440,7 +440,7 @@ test("sidebar reconnect prompt flips on live relay degradation without a query e test("profile popover does not show relay reconnect controls", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await page.getByTestId("sidebar-profile-avatar-button").click(); @@ -459,7 +459,7 @@ test("profile popover does not show relay reconnect controls", async ({ test("resume event short-circuits accumulated reconnect backoff", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await setMockWebsocketUnavailable(page, true); @@ -486,7 +486,7 @@ test("resume event short-circuits accumulated reconnect backoff", async ({ test("resume events during repeated AUTH rejection cannot defeat the terminal cap", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -546,7 +546,7 @@ test("resume events during repeated AUTH rejection cannot defeat the terminal ca test("sub-2s degraded flap invalidates relay queries on recovery", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await page.evaluate(() => { @@ -588,7 +588,7 @@ test("sub-2s degraded flap invalidates relay queries on recovery", async ({ test("transient AUTH rejection reconnects and restores live traffic", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -623,7 +623,7 @@ test("transient AUTH rejection reconnects and restores live traffic", async ({ test("auth-required CLOSED restores the active live subscription", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -651,7 +651,7 @@ test("auth-required CLOSED restores the active live subscription", async ({ test("reconnect backfills more missed channel messages than the live subscription limit", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/relay-restart.live.spec.ts b/desktop/tests/e2e/relay-restart.live.spec.ts index 1dbd7ea79d..058edb7290 100644 --- a/desktop/tests/e2e/relay-restart.live.spec.ts +++ b/desktop/tests/e2e/relay-restart.live.spec.ts @@ -1,7 +1,7 @@ import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { installBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { TwoRelayHarness, type RelaySpec } from "./helpers/twoRelayHarness"; @@ -166,7 +166,7 @@ test.describe("relay restart live gate", () => { relayHttpUrl, relayWsUrl: `ws://127.0.0.1:${spec.ports.main}`, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Baseline: the app converges to a live authenticated session and sees // the channel created for this fresh database. diff --git a/desktop/tests/e2e/release-smoke.spec.ts b/desktop/tests/e2e/release-smoke.spec.ts index 6b2429723f..e6128e2821 100644 --- a/desktop/tests/e2e/release-smoke.spec.ts +++ b/desktop/tests/e2e/release-smoke.spec.ts @@ -1,7 +1,13 @@ import { mkdir, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; -import { expect, test, type Page, type TestInfo } from "../helpers/test"; +import { + expect, + test, + type Page, + type TestInfo, + bootstrapE2ePage, +} from "../helpers/test"; import { installRelayBridge } from "../helpers/bridge"; import { assertRelaySeeded } from "../helpers/seed"; @@ -55,7 +61,7 @@ async function writeArtifact(testInfo: TestInfo, artifact: ScenarioArtifact) { async function openGeneral(page: Page) { await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page.locator('[data-render-pending="true"]')).toHaveCount(0); diff --git a/desktop/tests/e2e/reminder-click-repro.spec.ts b/desktop/tests/e2e/reminder-click-repro.spec.ts index 15f93bc8a8..6748c3670e 100644 --- a/desktop/tests/e2e/reminder-click-repro.spec.ts +++ b/desktop/tests/e2e/reminder-click-repro.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -56,7 +56,7 @@ async function expectAppAliveAfterDialogClose( async function openReminderDialogFromMessageMenu( page: import("@playwright/test").Page, ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -121,7 +121,7 @@ test.describe("reminder set → app stays clickable", () => { // dismissable layer are ever bundled again — body pointer-events stays // "none" after the dialog closes and the sidebar click below times out. test("03 — inbox row right-click → Remind me later", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox")).toBeVisible(); const row = page.getByTestId(INBOX_MENTION_ROW); diff --git a/desktop/tests/e2e/reminders.spec.ts b/desktop/tests/e2e/reminders.spec.ts index 44f94ffbeb..38bd734123 100644 --- a/desktop/tests/e2e/reminders.spec.ts +++ b/desktop/tests/e2e/reminders.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -8,7 +8,7 @@ const MOCK_PUBKEY = "deadbeef".repeat(8); // The inbox filter dropdown lives in the home pane, not the chat view. Land on // home and wait for the inbox before reaching for the filter trigger. async function gotoInboxHome(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox")).toBeVisible(); } @@ -78,7 +78,7 @@ test.describe("reminders", () => { }); test("02 — message action menu shows Remind me later", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -99,7 +99,7 @@ test.describe("reminders", () => { }); test("03 — Remind me later dialog with time presets", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/scroll-history.spec.ts b/desktop/tests/e2e/scroll-history.spec.ts index 702d8c939b..a10d609e8b 100644 --- a/desktop/tests/e2e/scroll-history.spec.ts +++ b/desktop/tests/e2e/scroll-history.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -72,7 +72,7 @@ test("channel switch settles at the newest message after virtualized rows measur page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -127,7 +127,7 @@ test("first channel load paints the first window without waiting for the row-flo page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -177,7 +177,7 @@ test("preserves user scroll while older channel history loads", async ({ }, testInfo) => { testInfo.setTimeout(60_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -357,7 +357,7 @@ test("does not teleport upward when user abandons fetch by jumping to bottom", a page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -571,7 +571,7 @@ test("reserves real buzz-bugs imeta image height before image loads", async ({ }) => { await page.route("**/media/**", () => new Promise(() => {})); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -633,7 +633,7 @@ test("deep-link to a message in older history scrolls and highlights it", async page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -842,7 +842,7 @@ test("unified channel search opens rows regardless of history position", async ( page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -1075,7 +1075,7 @@ test("composer expansion does not push bottom row out of viewport", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -1197,7 +1197,7 @@ test("mounted rows cover the viewport beneath the composer in both directions", page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -1288,7 +1288,7 @@ test("fast middle-page scroll settles with continuous mounted coverage", async ( page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -1402,7 +1402,7 @@ test("in-viewport reflow above the anchor row does not push it down", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -1521,7 +1521,7 @@ test("channel intro stays hidden while older history is loading", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -1622,7 +1622,7 @@ test("channel intro stays hidden while paginating past the timeline cap", async }, testInfo) => { testInfo.setTimeout(90_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -1755,7 +1755,7 @@ test("older-history fetches never overlap (no concurrent in-flight requests)", a page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -1821,7 +1821,7 @@ test("older-history spinner stays visible in viewport while fetching mid-scroll" page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -1906,7 +1906,7 @@ test("one scroll-up gesture pages older history once, not to the channel top", a }, testInfo) => { testInfo.setTimeout(60_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -2001,7 +2001,7 @@ test("older-history prepend keeps the reading row fixed (no jump to oldest)", as }, testInfo) => { testInfo.setTimeout(60_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -2124,7 +2124,7 @@ test("thread summary badge survives a retained older-history prepend", async ({ }, testInfo) => { testInfo.setTimeout(60_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/scroll-smoothness.perf.ts b/desktop/tests/e2e/scroll-smoothness.perf.ts index fc94a1225f..f2b0ea202a 100644 --- a/desktop/tests/e2e/scroll-smoothness.perf.ts +++ b/desktop/tests/e2e/scroll-smoothness.perf.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -59,7 +59,7 @@ test("MEASURE: fast-wheel scroll-up layout cost on a busy un-virtualized timelin page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && @@ -207,7 +207,7 @@ test("MEASURE: prepend re-render cost while scrolled up (the untested event-cost page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" && diff --git a/desktop/tests/e2e/scrollback-buzzbugs.perf.ts b/desktop/tests/e2e/scrollback-buzzbugs.perf.ts index ff7d389d99..9aa6f0e7c8 100644 --- a/desktop/tests/e2e/scrollback-buzzbugs.perf.ts +++ b/desktop/tests/e2e/scrollback-buzzbugs.perf.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { decode } from "nostr-tools/nip19"; import { getPublicKey } from "nostr-tools/pure"; @@ -179,7 +179,7 @@ test("MEASURE: scroll-back pagination latency in target channel", async ({ ); // ---- Boot to sidebar, open the target channel ---- - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("app-sidebar").waitFor({ state: "visible" }); const chan = page.getByTestId(`channel-${TARGET_CHANNEL}`).first(); await chan.waitFor({ state: "visible", timeout: 45_000 }); diff --git a/desktop/tests/e2e/search-scope-screenshots.spec.ts b/desktop/tests/e2e/search-scope-screenshots.spec.ts index f35ee6f0fc..dbb0362679 100644 --- a/desktop/tests/e2e/search-scope-screenshots.spec.ts +++ b/desktop/tests/e2e/search-scope-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -7,7 +7,7 @@ test("captures global and current-channel search scope states", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-deep-history").click(); await expect(page.getByTestId("chat-title")).toHaveText("deep-history"); await expect( diff --git a/desktop/tests/e2e/send-channel-binding.spec.ts b/desktop/tests/e2e/send-channel-binding.spec.ts index 8ac488f7d9..1e88d7195c 100644 --- a/desktop/tests/e2e/send-channel-binding.spec.ts +++ b/desktop/tests/e2e/send-channel-binding.spec.ts @@ -10,7 +10,7 @@ * handler open long enough for the channel click to race the in-flight send. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -68,7 +68,7 @@ test("message with agent mention lands in compose-time channel despite mid-send ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -156,7 +156,7 @@ test("message with agent mention delivers correctly when no channel switch occur ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/settings-section-layout.spec.ts b/desktop/tests/e2e/settings-section-layout.spec.ts index 2e9b1915b8..8317db4748 100644 --- a/desktop/tests/e2e/settings-section-layout.spec.ts +++ b/desktop/tests/e2e/settings-section-layout.spec.ts @@ -1,11 +1,11 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; test("settings sections share the Appearance rhythm", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "appearance"); const appearanceList = page @@ -84,7 +84,7 @@ test("Profile sections keep visible cards and aligned actions", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page, "profile"); const identity = page.getByTestId("profile-identity-card"); diff --git a/desktop/tests/e2e/sidebar-more-unread-overlap.spec.ts b/desktop/tests/e2e/sidebar-more-unread-overlap.spec.ts index ee79896a00..8af3bd7177 100644 --- a/desktop/tests/e2e/sidebar-more-unread-overlap.spec.ts +++ b/desktop/tests/e2e/sidebar-more-unread-overlap.spec.ts @@ -14,7 +14,7 @@ * container (the same relative wrapper that owns the real top unread pill) and * asserts the pill clears the pinned header. */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -65,7 +65,7 @@ async function injectSyntheticPill( test.describe("sidebar MoreUnreadButton top chrome overlap", () => { test.beforeEach(async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); }); diff --git a/desktop/tests/e2e/sidebar-offcanvas-rail.spec.ts b/desktop/tests/e2e/sidebar-offcanvas-rail.spec.ts index 1936aff18b..d6b4386e1f 100644 --- a/desktop/tests/e2e/sidebar-offcanvas-rail.spec.ts +++ b/desktop/tests/e2e/sidebar-offcanvas-rail.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -36,7 +36,7 @@ async function setup(page: Page, theme: string) { }, { list: [COMMUNITY_A, COMMUNITY_B], active: COMMUNITY_A.id }, ); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("community-rail")).toBeVisible(); await expect(page.getByTestId("app-sidebar")).toBeVisible(); } diff --git a/desktop/tests/e2e/sidebar-relay-card.spec.ts b/desktop/tests/e2e/sidebar-relay-card.spec.ts index b3c833f981..0dd3f392de 100644 --- a/desktop/tests/e2e/sidebar-relay-card.spec.ts +++ b/desktop/tests/e2e/sidebar-relay-card.spec.ts @@ -1,4 +1,4 @@ -import { expect, type Page, test } from "../helpers/test"; +import { expect, type Page, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -117,7 +117,7 @@ test("sidebar generic relay failures use the reconnect card", async ({ }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // The card is gated on !isRelayConnectionConnected — a stale channelsReadError // alone (with state still "connected") no longer pins the card. Drive the relay @@ -132,7 +132,7 @@ test("relay outage notification stays dismissed through retries and re-arms afte page, }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await setRelayConnectionState(page, "disconnected"); const card = await expectGenericReconnectCard(page); @@ -160,7 +160,7 @@ test("relay outage notification re-arms after same-URL lifecycle teardown", asyn page, }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await setRelayConnectionState(page, "disconnected"); const card = await expectGenericReconnectCard(page); @@ -182,7 +182,7 @@ test("sidebar proxy sign-in failures use the reconnect card", async ({ }) => { await installMockBridge(page, { channelsReadError: PROXY_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive disconnected so the card appears immediately (connected is now authoritative). await setRelayConnectionState(page, "disconnected"); @@ -193,7 +193,7 @@ test("sidebar proxy sign-in failures use the reconnect card", async ({ test("sidebar access failures use the reconnect card", async ({ page }) => { await installMockBridge(page, { channelsReadError: ACCESS_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive disconnected so the card appears immediately (connected is now authoritative). await setRelayConnectionState(page, "disconnected"); @@ -204,7 +204,7 @@ test("sidebar access failures use the reconnect card", async ({ page }) => { test("collapsed sidebar still shows the reconnect card", async ({ page }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive degraded state so the card appears. await setRelayConnectionState(page, "disconnected"); @@ -236,7 +236,7 @@ test("sidebar stalled relay state uses the reconnect card", async ({ }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await setRelayConnectionState(page, "stalled"); @@ -248,7 +248,7 @@ test("sidebar application auth disconnects stay on the error path", async ({ }) => { await installMockBridge(page, { channelsReadError: RELAY_AUTH_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await setRelayConnectionState(page, "disconnected"); await expect(page.getByText(RELAY_AUTH_ERROR)).toBeVisible(); @@ -260,7 +260,7 @@ test("sidebar reconnect action shows connected before hiding", async ({ }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive disconnected state so the card appears immediately. await setRelayConnectionState(page, "disconnected"); @@ -285,7 +285,7 @@ test("sidebar reconnect action suppresses stale refresh errors after success", a }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive disconnected state so the card appears immediately. await setRelayConnectionState(page, "disconnected"); @@ -310,7 +310,7 @@ test("sidebar connected success clears when relay degrades again", async ({ }) => { await installMockBridge(page, { channelsReadError: CONNECT_ERROR }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Drive disconnected state so the card appears immediately. await setRelayConnectionState(page, "disconnected"); diff --git a/desktop/tests/e2e/sidebar-snapshot.spec.ts b/desktop/tests/e2e/sidebar-snapshot.spec.ts index eb5b83f3b8..604b93e5e6 100644 --- a/desktop/tests/e2e/sidebar-snapshot.spec.ts +++ b/desktop/tests/e2e/sidebar-snapshot.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -250,7 +250,7 @@ test("cold boot paints the complete snapshot, sends its hash, and revalidates", }); const navigationStartedAt = performance.now(); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rows = page.locator('[data-channel-id^="snapshot-"]'); await expect(rows).toHaveCount(FULL_SNAPSHOT.length, { timeout: 2_000 }); const snapshotPaintedAt = performance.now(); @@ -298,7 +298,7 @@ test("matching not-modified preserves display mutations without persisting them" channelsReadDelayMs: READ_DELAY_MS, honorChannelsKnownHash: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount( FULL_SNAPSHOT.length, @@ -330,7 +330,7 @@ test("first-ever boot without a snapshot sends null and shows loading", async ({ page, }) => { await installMockBridge(page, { channelsReadDelayMs: READ_DELAY_MS }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("sidebar-loading")).toBeVisible({ timeout: 500, @@ -363,7 +363,7 @@ test("a different identity's snapshot is ignored", async ({ page }) => { ownerPubkey: STALE_COMMUNITY_PUBKEY, }); await installMockBridge(page, { channelsReadDelayMs: READ_DELAY_MS }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("sidebar-loading")).toBeVisible({ timeout: 500, @@ -395,7 +395,7 @@ test("display-only community pubkey cannot expose another identity's snapshot", { skipCommunitySeed: true }, ); await seedCommunities(page, STALE_COMMUNITY_PUBKEY); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); expect(await getTrackedSnapshotRows(page)).toEqual([]); @@ -421,7 +421,7 @@ test("partial hash/list write fails toward a full fetch", async ({ page }) => { channelsReadDelayMs: READ_DELAY_MS, honorChannelsKnownHash: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("sidebar-loading")).toBeVisible({ timeout: 500, @@ -446,7 +446,7 @@ test("unexpected not-modified response retries without a hash", async ({ await installMockBridge(page, { channelsNotModifiedResponses: 1, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("channel-general")).toBeVisible(); await expect @@ -462,7 +462,7 @@ test("mismatched not-modified hash falls back to a full list", async ({ channelsReadDelayMs: READ_DELAY_MS, channelsNotModifiedResponses: 1, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const snapshotRows = page.locator('[data-channel-id^="snapshot-"]'); await expect(snapshotRows).toHaveCount(FULL_SNAPSHOT.length, { @@ -507,7 +507,7 @@ test("hash mismatch replaces the snapshot with the full live list", async ({ channelsReadDelayMs: READ_DELAY_MS, honorChannelsKnownHash: true, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount( FULL_SNAPSHOT.length, @@ -561,7 +561,7 @@ test("community switch validates a stale relay snapshot and replaces it", async { skipCommunitySeed: true }, ); await seedCommunities(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); const payloadCountBeforeSwitch = (await getChannelsPayloads(page)).length; diff --git a/desktop/tests/e2e/sidebar.spec.ts b/desktop/tests/e2e/sidebar.spec.ts index 53b25a4e6a..39d5bbc429 100644 --- a/desktop/tests/e2e/sidebar.spec.ts +++ b/desktop/tests/e2e/sidebar.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -30,7 +30,7 @@ async function loadTheme(page: Page, theme: string) { window.localStorage.setItem("buzz-theme", selectedTheme); }, theme); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); } async function openAddCommunityDialog(page: Page) { @@ -197,7 +197,7 @@ test("sidebar rows separate hover, selected, and reorder states", async ({ test("add community starts with create and join choices", async ({ page }) => { await installMockBridge(page, {}); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openAddCommunityDialog(page); @@ -241,7 +241,7 @@ test("automatically shows community join requirements near the community URL", a version: "policy-v1", }, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openAddCommunityDialog(page); await page.getByTestId("add-community-join").click(); @@ -290,7 +290,7 @@ test("automatically shows community join requirements near the community URL", a test("joins a community URL without an API token field", async ({ page }) => { await installMockBridge(page, { applyCommunityDelayMs: 1_000 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openAddCommunityDialog(page); await page.getByTestId("add-community-join").click(); @@ -314,7 +314,7 @@ test("joins a community URL without an API token field", async ({ page }) => { }); test("hides Invites settings on open relays", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSettings(page); await expect(page.getByTestId("settings-nav-community-members")).toHaveCount( @@ -325,7 +325,7 @@ test("hides Invites settings on open relays", async ({ page }) => { test("leaving a channel from the context menu never freezes the app", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); // Cancel path: dialog opens from the context menu, then is dismissed. @@ -348,7 +348,7 @@ test("leaving a channel from the context menu never freezes the app", async ({ test("channel context menu only shows owner actions to the owner", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click({ button: "right" }); await expect( @@ -388,7 +388,7 @@ test("channel context menu explains when owner actions are loading", async ({ page, }) => { await installMockBridge(page, { channelMembersReadDelayMs: 500 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click({ button: "right" }); await expect( @@ -403,7 +403,7 @@ test("channel context menu explains when owner actions are loading", async ({ }); test("channel owner can archive from the context menu", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click({ button: "right" }); await page.getByRole("menuitem", { name: "Archive channel" }).click(); @@ -412,7 +412,7 @@ test("channel owner can archive from the context menu", async ({ page }) => { }); test("channel owner can delete from the context menu", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await page.getByTestId("channel-general").click({ button: "right" }); @@ -601,7 +601,7 @@ test("disables the sidebar collapse transition for reduced motion", async ({ }); test("sidebar rail resizes without toggling the sidebar", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rail = page.getByRole("button", { name: "Resize sidebar" }); await rail.click(); await expect(page.getByTestId("app-sidebar")).toBeVisible(); @@ -613,7 +613,7 @@ test("sidebar rail resizes without toggling the sidebar", async ({ page }) => { test("resizes, persists, and snaps to the default sidebar width", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await expect.poll(() => sidebarWidth(page)).toBe(DEFAULT_SIDEBAR_WIDTH); @@ -639,7 +639,7 @@ test("resizes, persists, and snaps to the default sidebar width", async ({ test("shows a sidebar update card when an update is ready", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await page.evaluate(() => { @@ -734,7 +734,7 @@ test("shows a sidebar update card when an update is ready", async ({ test("reflects an install started from the header update button on the sidebar card", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await page.evaluate(() => { @@ -783,7 +783,7 @@ test("reflects an install started from the header update button on the sidebar c test("shows manual-required update card and never auto-downloads on non-AppImage installs", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); // Override the bridge to report an update available AND auto-update not diff --git a/desktop/tests/e2e/signout-confirmation.spec.ts b/desktop/tests/e2e/signout-confirmation.spec.ts index 60393e3608..e425d5bf7c 100644 --- a/desktop/tests/e2e/signout-confirmation.spec.ts +++ b/desktop/tests/e2e/signout-confirmation.spec.ts @@ -6,7 +6,7 @@ * 1. backup — check "I have saved my private key" * 2. typed confirmation — type the exact phrase "wipe all my data" */ -import { expect, type Page, test } from "../helpers/test"; +import { expect, type Page, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -25,7 +25,7 @@ test("delete button unlocks only after backup + typed phrase", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSignOutDialog(page); const deleteButton = page.getByTestId("signout-confirm"); @@ -55,7 +55,7 @@ test("delete button unlocks only after backup + typed phrase", async ({ test("completing both gates invokes sign_out", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSignOutDialog(page); await page.getByTestId("signout-backup-confirm").click(); @@ -79,7 +79,7 @@ test("completing both gates invokes sign_out", async ({ page }) => { test("cancel resets the gates for the next open", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSignOutDialog(page); // Satisfy both gates, then cancel. @@ -100,7 +100,7 @@ test("nsec load failure still allows sign-out (backup step degrades)", async ({ page, }) => { await installMockBridge(page, { nsecError: "Keychain locked" }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openSignOutDialog(page); // Error shown in place of the key; checkbox is still usable so the user is diff --git a/desktop/tests/e2e/signout-screenshots.spec.ts b/desktop/tests/e2e/signout-screenshots.spec.ts index fc9ca95ae9..eea0e39a1d 100644 --- a/desktop/tests/e2e/signout-screenshots.spec.ts +++ b/desktop/tests/e2e/signout-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { openSettings } from "../helpers/settings"; @@ -27,7 +27,7 @@ test.describe("signout screenshots", () => { page, }) => { await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "profile"); const section = page.getByTestId("settings-signout"); @@ -49,7 +49,7 @@ test.describe("signout screenshots", () => { page, }) => { await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "profile"); const section = page.getByTestId("settings-signout"); diff --git a/desktop/tests/e2e/smoke.spec.ts b/desktop/tests/e2e/smoke.spec.ts index 276ccbad2b..c538a179d7 100644 --- a/desktop/tests/e2e/smoke.spec.ts +++ b/desktop/tests/e2e/smoke.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, openCreateChannelDialog } from "../helpers/bridge"; @@ -85,7 +85,7 @@ test.beforeEach(async ({ page }) => { }); test("loads the app shell with mocked channels", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("app-sidebar")).toBeVisible(); await expect(page.getByTestId("stream-list")).toContainText("general"); @@ -111,7 +111,7 @@ async function chooseSharedComputeProvider( test("creates a new mocked stream", async ({ page }) => { const channelName = `release-notes-${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page @@ -126,7 +126,7 @@ test("creates a new mocked stream", async ({ page }) => { test("Buzz shared compute explains automatic model selection", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.evaluate(() => { ( window as Window & { @@ -163,7 +163,7 @@ test("create agent persists Buzz shared compute with auto model", async ({ }) => { const agentName = `Shared compute agent ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); await page.locator("#persona-display-name").fill(agentName); @@ -209,7 +209,7 @@ test("create agent supports parallelism and system prompt overrides", async ({ }) => { const agentName = `Parallel agent ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); @@ -282,7 +282,7 @@ test("create agent supports parallelism and system prompt overrides", async ({ test("opens a mocked channel from the inbox feed", async ({ page }) => { const inboxList = page.getByTestId("home-inbox-list"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectHomeView(page); await expect(inboxList).toContainText("Please review the release checklist."); @@ -302,7 +302,7 @@ test("Inbox excludes generic channel and unowned agent traffic", async ({ }) => { const inboxList = page.getByTestId("home-inbox-list"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectHomeView(page); await expect(inboxList).not.toContainText( @@ -317,7 +317,7 @@ test("Inbox excludes generic channel and unowned agent traffic", async ({ }); test("inbox feed renders resolved author labels", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toContainText("alice"); await expect(page.getByTestId("home-inbox-list")).not.toContainText("You"); @@ -326,7 +326,7 @@ test("inbox feed renders resolved author labels", async ({ page }) => { test("opens sidebar search with the shortcut and loads the exact result", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); @@ -347,7 +347,7 @@ test("opens sidebar search with the shortcut and loads the exact result", async }); test("opens channel matches from search", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); @@ -382,7 +382,7 @@ test("opens channel matches from search", async ({ page }) => { test("global search offers an optional current-channel scope", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect(page).toHaveURL( @@ -475,7 +475,7 @@ test("global search offers a conversation-specific scope in direct messages", as }) => { const directMessageId = "f48efb06-0c93-5025-aac9-2e646bb6bfa8"; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-alice-tyler").click(); await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler"); await expect(page).toHaveURL( @@ -555,7 +555,7 @@ test("global search offers a conversation-specific scope in direct messages", as test("channel find shortcut opens unified search with scope selected", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page).toHaveURL( /#\/channels\/9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50$/, @@ -576,7 +576,7 @@ test("channel find shortcut opens unified search with scope selected", async ({ test("global search omits channel scoping when no channel is active", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectHomeView(page); await focusSidebarSearchWithShortcut(page); @@ -590,7 +590,7 @@ test("global search omits channel scoping when no channel is active", async ({ test("global one-character search does not query the relay", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); await page.getByTestId("search-dialog-input").fill("x"); @@ -611,7 +611,7 @@ test("global one-character search does not query the relay", async ({ test("global search tolerates small channel and people typos", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); const input = page.getByTestId("search-dialog-input"); @@ -626,7 +626,7 @@ test("global search tolerates small channel and people typos", async ({ test("global search exposes a larger scrollable result window", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-deep-history").click(); await expect(page.getByTestId("chat-title")).toHaveText("deep-history"); await expect( @@ -667,7 +667,7 @@ test("global search exposes a larger scrollable result window", async ({ }); test("closes sidebar search with Escape", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); await page.getByTestId("search-dialog-input").fill("shipped"); @@ -681,7 +681,7 @@ test("closes sidebar search with Escape", async ({ page }) => { test("search shortcut opens search without disturbing the collapsed sidebar", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("open-search")).toBeVisible(); @@ -716,7 +716,7 @@ test("search shortcut opens search without disturbing the collapsed sidebar", as test("search results use your resolved profile label instead of You", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); @@ -731,7 +731,7 @@ test("search results use your resolved profile label instead of You", async ({ test("opens accessible unjoined channels from search in read-only mode", async ({ page, }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await focusSidebarSearchWithShortcut(page); @@ -751,7 +751,7 @@ test("opens accessible unjoined channels from search in read-only mode", async ( }); test("replaces the channel pane when switching channels", async ({ page }) => { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -781,7 +781,7 @@ test("replaces the channel pane when switching channels", async ({ page }) => { test("sends a mocked channel message", async ({ page }) => { const message = `Smoke message ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("message-input").fill(message); @@ -819,7 +819,7 @@ test("supports multiline drafts with Ctrl+Enter and sends with Enter", async ({ ]; const input = page.getByTestId("message-input"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await expect( @@ -858,7 +858,7 @@ test("does not shift the timeline when the composer grows", async ({ const input = page.getByTestId("message-input"); const prefix = `Composer growth ${Date.now()}`; - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/spoiler.spec.ts b/desktop/tests/e2e/spoiler.spec.ts index 2879e45be2..2f56bc3692 100644 --- a/desktop/tests/e2e/spoiler.spec.ts +++ b/desktop/tests/e2e/spoiler.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Page } from "@playwright/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; @@ -52,7 +52,7 @@ test("no-selection spoiler applies to every composer paragraph", async ({ origin: "http://127.0.0.1:4173", }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -92,7 +92,7 @@ test("image attachments can be marked and sent as hidden spoilers", async ({ page, }) => { await installSpoilerBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -128,7 +128,7 @@ test("text spoiler stays usable while attachment upload is pending", async ({ page, }) => { await installSpoilerBridge(page, { uploadDelayMs: 1_000 }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -163,7 +163,7 @@ test("hidden spoiler links reveal without opening on the first click", async ({ page, }) => { await installSpoilerBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -213,7 +213,7 @@ test("hidden spoilers stay masked on hover and focus until reveal", async ({ page, }) => { await installSpoilerBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -254,7 +254,7 @@ test("masked link inside a hidden spoiler does not leak its URL until revealed", }) => { const SECRET_URL = "https://private.example/leak-path"; await installSpoilerBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -312,7 +312,7 @@ test("non-interactive inbox preview spoilers let row clicks pass through", async page, }) => { await installSpoilerBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); await page.waitForFunction( () => diff --git a/desktop/tests/e2e/stream.spec.ts b/desktop/tests/e2e/stream.spec.ts index e6a2591e5e..a1a7f6eebe 100644 --- a/desktop/tests/e2e/stream.spec.ts +++ b/desktop/tests/e2e/stream.spec.ts @@ -1,4 +1,10 @@ -import { expect, test, type Browser, type Page } from "../helpers/test"; +import { + expect, + test, + type Browser, + type Page, + bootstrapE2ePage, +} from "../helpers/test"; import { installRelayBridge, @@ -191,7 +197,7 @@ test.beforeAll(async () => { test("loads channels from the relay", async ({ page }) => { await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("stream-list")).toContainText("general"); await expect(page.getByTestId("stream-list")).toContainText("random"); @@ -209,7 +215,7 @@ test("loads the home feed from the relay", async ({ browser }) => { try { await installRelayBridge(page, "tyler"); await installRelayBridge(senderPage, "alice"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await senderPage.goto("/"); await expect(page.getByTestId("home-inbox")).toBeVisible(); @@ -242,7 +248,7 @@ test("shows sent inbox replies immediately in the inbox detail pane", async ({ try { await installRelayBridge(page, "tyler"); await installRelayBridge(senderPage, "alice"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await senderPage.goto("/"); await sendChannelMessage(senderPage, { @@ -269,7 +275,7 @@ test("creates a relay-backed stream", async ({ page }) => { const channelName = `desktop-e2e-${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill(channelName); await page @@ -285,7 +291,7 @@ test("sends a message through the real relay", async ({ page }) => { const message = `Integration message ${Date.now()}`; await installRelayBridge(page, "tyler"); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("message-input").fill(message); diff --git a/desktop/tests/e2e/team-mentions.spec.ts b/desktop/tests/e2e/team-mentions.spec.ts index a3cee2f7fb..cea988ef3c 100644 --- a/desktop/tests/e2e/team-mentions.spec.ts +++ b/desktop/tests/e2e/team-mentions.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { waitForAnimations } from "../helpers/animations"; @@ -36,7 +36,7 @@ test("owned team mention unfurls into its agents", async ({ page }) => { ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); const composer = page.getByTestId("message-composer"); diff --git a/desktop/tests/e2e/team-snapshot.spec.ts b/desktop/tests/e2e/team-snapshot.spec.ts index a9ac9757ad..d141869fd1 100644 --- a/desktop/tests/e2e/team-snapshot.spec.ts +++ b/desktop/tests/e2e/team-snapshot.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge, @@ -24,7 +24,7 @@ async function readCommandLog(page: import("@playwright/test").Page) { } async function gotoAgentsPage(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-agents-view").click(); } diff --git a/desktop/tests/e2e/terminal-wheel.spec.ts b/desktop/tests/e2e/terminal-wheel.spec.ts index 67288fda5b..f65b00af8b 100644 --- a/desktop/tests/e2e/terminal-wheel.spec.ts +++ b/desktop/tests/e2e/terminal-wheel.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; const TERM = 'section[aria-label="Buzz Term"]'; @@ -145,7 +145,7 @@ async function reveal(page: Page) { await page.setViewportSize({ width: 1280, height: 800 }); await installTerminalBackend(page); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expect(page.getByTestId("home-inbox-list")).toBeVisible(); // Buzz Term needs a channel: TerminalBootstrap's context is null on Home, so // no session spawns and the chord is inert. diff --git a/desktop/tests/e2e/thread-focus-mode.spec.ts b/desktop/tests/e2e/thread-focus-mode.spec.ts index a0d2a29919..696bc9cd4e 100644 --- a/desktop/tests/e2e/thread-focus-mode.spec.ts +++ b/desktop/tests/e2e/thread-focus-mode.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -173,7 +173,7 @@ test("focus and split preserve reading context and interaction ownership", async }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rootId = await seedLongThread(page); await page.getByTestId("channel-general").click(); @@ -283,7 +283,7 @@ test("narrow threads do not offer an unavailable layout switch", async ({ }) => { await page.setViewportSize({ width: 860, height: 720 }); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); const rootId = await seedLongThread(page); await page.getByTestId("channel-general").click(); const summary = page.locator( diff --git a/desktop/tests/e2e/thread-reply-anchor-roleplay.spec.ts b/desktop/tests/e2e/thread-reply-anchor-roleplay.spec.ts index 70f418c243..6ea0a20d24 100644 --- a/desktop/tests/e2e/thread-reply-anchor-roleplay.spec.ts +++ b/desktop/tests/e2e/thread-reply-anchor-roleplay.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -123,7 +123,7 @@ async function setupRoleplayChannel(page: import("@playwright/test").Page) { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText(CHANNEL); await waitForMockLiveSubscription(page, CHANNEL); diff --git a/desktop/tests/e2e/thread-unread.spec.ts b/desktop/tests/e2e/thread-unread.spec.ts index 496da08af5..bfe2a8df5f 100644 --- a/desktop/tests/e2e/thread-unread.spec.ts +++ b/desktop/tests/e2e/thread-unread.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -112,7 +112,7 @@ async function expandReply( test.describe("thread unread indicator", () => { test("01-thread-unread-badge", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Open general — catch-up adds mock-general-welcome to authoredRootIds await page.getByTestId("channel-general").click(); @@ -169,7 +169,7 @@ test.describe("thread unread indicator", () => { test("02-thread-new-divider", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -220,7 +220,7 @@ test.describe("thread unread indicator", () => { test("03-thread-badge-casual-browse", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -270,7 +270,7 @@ test.describe("thread unread indicator", () => { test("04-thread-deep-nested-unread", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -403,7 +403,7 @@ test.describe("thread unread indicator", () => { test("05-thread-in-panel-subtree-badge", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -504,7 +504,7 @@ test.describe("thread unread indicator", () => { test("06-in-panel-badge-bumps-on-live-reply", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -569,7 +569,7 @@ test.describe("thread unread indicator", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -664,7 +664,7 @@ test.describe("thread unread indicator", () => { // filter on activeReadAt this fails on the second entry. test("10-thread-badge-survives-channel-reentry", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -717,7 +717,7 @@ test.describe("thread unread indicator", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Open general and read its thread frontier, so the only thing that can be // unread afterward is a NEW reply — not the channel timeline. @@ -763,7 +763,7 @@ test.describe("thread unread indicator", () => { // window. test("12-thread-reply-lights-all-replies-sidebar-badge", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Emit ONE reply whose parent root is NOT in the window (orphan parent id), // so the loaded window is all-replies: no top-level message exists for @@ -808,7 +808,7 @@ test.describe("thread unread indicator", () => { // while staying in general. test("13-thread-badge-clears-on-read-without-reentry", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -872,7 +872,7 @@ test.describe("thread unread indicator", () => { // after the subtree-count fix does it reach 2. Asserting `2` gates both. test("14-mention-only-nested-thread-badge", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -950,7 +950,7 @@ test.describe("thread unread indicator", () => { // which is exactly what the toggle's forced-unread overlay exists to drive. test("15-mark-read-unread-menu-single-toggle", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/threadpane-ultrawide.spec.ts b/desktop/tests/e2e/threadpane-ultrawide.spec.ts index b89e66617d..a04b7df6cc 100644 --- a/desktop/tests/e2e/threadpane-ultrawide.spec.ts +++ b/desktop/tests/e2e/threadpane-ultrawide.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -62,7 +62,7 @@ async function emitMockReply( } async function openThread(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); diff --git a/desktop/tests/e2e/timeline-no-shift.spec.ts b/desktop/tests/e2e/timeline-no-shift.spec.ts index 0ec3b50139..9a2b631877 100644 --- a/desktop/tests/e2e/timeline-no-shift.spec.ts +++ b/desktop/tests/e2e/timeline-no-shift.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Locator, Page } from "@playwright/test"; import { installMockBridge } from "../helpers/bridge"; @@ -206,7 +206,7 @@ test("timeline does not recompute row estimates during ordinary scroll", async ( page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockTimelineBridge(page); await page.evaluate(() => { for (let index = 0; index < 120; index += 1) { @@ -265,7 +265,7 @@ test("timeline reserves mixed-media rows before fast scrollback", async ({ contentType: "image/svg+xml", }); }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockTimelineBridge(page); await page.evaluate(() => { window.__BUZZ_E2E__ = { @@ -446,7 +446,7 @@ test("timeline prepend plus late row reflow keeps the reading row stable", async testInfo.setTimeout(45_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockTimelineBridge(page); await seedNoShiftTimeline(page); @@ -603,7 +603,7 @@ test("thread panel late row reflow keeps the reading reply stable", async ({ testInfo.setTimeout(45_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockTimelineBridge(page); const rootId = await page.evaluate(() => { @@ -696,7 +696,7 @@ test("thread panel stays put while replies stream in mid-scroll", async ({ testInfo.setTimeout(45_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await waitForMockTimelineBridge(page); page.on("console", (msg) => { diff --git a/desktop/tests/e2e/top-chrome-zoom-clearance.spec.ts b/desktop/tests/e2e/top-chrome-zoom-clearance.spec.ts index fb990c74a5..1a9116b224 100644 --- a/desktop/tests/e2e/top-chrome-zoom-clearance.spec.ts +++ b/desktop/tests/e2e/top-chrome-zoom-clearance.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { readFileSync } from "node:fs"; import { installMockBridge } from "../helpers/bridge"; @@ -116,7 +116,7 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () => }) => { await spoofMacPlatform(page); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Lock the native and webview placements together: removing this explicit // Tauri inset or shifting the nav row regresses the macOS chrome alignment. @@ -150,7 +150,7 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () => // fixed-position native controls. await seedTextScale(page, 0.75); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Confirm the zoomed-out text scale applied without changing the root. await expectTextRemSize(page, "12px"); @@ -168,7 +168,7 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () => // here) grew visibly taller than the fixed-size traffic lights. await seedTextScale(page, 1.5); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await expectTextRemSize(page, "24px"); diff --git a/desktop/tests/e2e/typing-latency.perf.ts b/desktop/tests/e2e/typing-latency.perf.ts index b5f09b7056..a6b92f036b 100644 --- a/desktop/tests/e2e/typing-latency.perf.ts +++ b/desktop/tests/e2e/typing-latency.perf.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -118,7 +118,7 @@ test("MEASURE: composer keystroke latency, quiet vs agent-busy channel", async ( }) => { test.setTimeout(240_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/unread-pill.spec.ts b/desktop/tests/e2e/unread-pill.spec.ts index 0aaf4f0ea9..45379eca73 100644 --- a/desktop/tests/e2e/unread-pill.spec.ts +++ b/desktop/tests/e2e/unread-pill.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge"; @@ -101,7 +101,7 @@ async function scrollTimelineUp(page: import("@playwright/test").Page) { test.describe("unread pill & divider", () => { test("01-unread-pill-visible", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); // Open general, then switch to random so general becomes inactive await page.getByTestId("channel-general").click(); @@ -149,7 +149,7 @@ test.describe("unread pill & divider", () => { test("02-unread-divider-visible", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -173,7 +173,7 @@ test.describe("unread pill & divider", () => { test("03-pill-dismissed-after-scroll", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -205,7 +205,7 @@ test.describe("unread pill & divider", () => { test("04-mark-unread-suppresses-pill", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); diff --git a/desktop/tests/e2e/video-attachment.spec.ts b/desktop/tests/e2e/video-attachment.spec.ts index 61c955bff2..d5802f9c13 100644 --- a/desktop/tests/e2e/video-attachment.spec.ts +++ b/desktop/tests/e2e/video-attachment.spec.ts @@ -1,4 +1,4 @@ -import { expect, type Page, test } from "../helpers/test"; +import { expect, type Page, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css"; @@ -226,7 +226,7 @@ async function openReviewWithPostedTimecode( page: Page, commentText = "Neutral accent check", ) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -281,7 +281,7 @@ test("video upload previews use poster frames and inline videos open review mode themeName: VIDEO_REVIEW_ACCENT_THEME, }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -840,7 +840,7 @@ test("inline video hover reveals a timeline without a second play control", asyn }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -937,7 +937,7 @@ test("video replies in threads open the review comments view", async ({ }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1104,7 +1104,7 @@ test("Inbox preserves bracketed timestamps without video evidence", async ({ }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1133,7 +1133,7 @@ test("Inbox recognizes reference-style video ancestors with custom alt text", as }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1175,7 +1175,7 @@ test("message timecodes deterministically open the first attached video", async }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1243,7 +1243,7 @@ test("message timecodes deterministically open the first attached video", async test("narrow inline videos hide playback speed control", async ({ page }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1288,7 +1288,7 @@ test("constrained landscape inline videos measure rendered width before showing }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); @@ -1404,7 +1404,7 @@ test("right-click menus expose distinct selectors for links, relay video, and of }) => { await installVideoReviewHarness(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await waitForMockLiveSubscription(page, "general"); diff --git a/desktop/tests/e2e/virtualization.spec.ts b/desktop/tests/e2e/virtualization.spec.ts index e2c1b3fb60..1edefc6e16 100644 --- a/desktop/tests/e2e/virtualization.spec.ts +++ b/desktop/tests/e2e/virtualization.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import type { Locator, Page } from "@playwright/test"; import { installMockBridge } from "../helpers/bridge"; @@ -48,7 +48,7 @@ test.describe("list virtualization", () => { page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-pulse-view").click(); // The seeded feed overflows the viewport (30 notes), so the windowed list @@ -84,7 +84,8 @@ test.describe("list virtualization", () => { test("02 — forum deep-link lands on an offscreen reply", async ({ page }) => { await installMockBridge(page); - await page.goto( + await bootstrapE2ePage( + page, `/#/channels/${WATERCOOLER_CHANNEL_ID}/posts/${FORUM_THREAD_ID}?replyId=${FORUM_DEEPLINK_REPLY_ID}`, ); @@ -120,7 +121,7 @@ test.describe("list virtualization", () => { }, ], }); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); await page.getByTestId("channel-members-trigger").click(); @@ -148,7 +149,7 @@ test.describe("list virtualization", () => { }) => { await seedChannelSections(page); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("channel-general").click(); await expect(page.getByTestId("chat-title")).toHaveText("general"); @@ -200,7 +201,10 @@ test.describe("list virtualization", () => { deepHistoryMessageCount: 1_800, channelWindowDelayMs: 300, }); - await page.goto("/#/channels/feedf00d-0000-4000-8000-000000000007"); + await bootstrapE2ePage( + page, + "/#/channels/feedf00d-0000-4000-8000-000000000007", + ); const timeline = page.getByTestId("message-timeline"); await expect(timeline.locator("[data-message-id]").first()).toBeVisible(); // Initial bottom positioning can momentarily cross the start threshold. Let @@ -442,7 +446,10 @@ test.describe("list virtualization", () => { deepHistoryMessageCount: 1_800, channelWindowDelayMs: 300, }); - await page.goto("/#/channels/feedf00d-0000-4000-8000-000000000007"); + await bootstrapE2ePage( + page, + "/#/channels/feedf00d-0000-4000-8000-000000000007", + ); const timeline = page.getByTestId("message-timeline"); await expect(timeline.locator("[data-message-id]").first()).toBeVisible(); await page.waitForTimeout(1_000); @@ -565,7 +572,7 @@ test.describe("list virtualization", () => { test("thread-heavy history mounts every loaded row", async ({ page }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -617,7 +624,7 @@ test("channel switches settle the last row above the composer", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -667,7 +674,7 @@ test("offscreen rich-row resize preserves the viewport-center anchor", async ({ page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -752,7 +759,7 @@ test("live tail arrivals keep a bottom-pinned virtual timeline settled", async ( page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); @@ -802,7 +809,7 @@ test("live tail arrivals stay buffered while reading and release on jump", async page, }) => { await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/voice-settings.spec.ts b/desktop/tests/e2e/voice-settings.spec.ts index a17714169d..65b0e6e32c 100644 --- a/desktop/tests/e2e/voice-settings.spec.ts +++ b/desktop/tests/e2e/voice-settings.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; @@ -13,7 +13,7 @@ test.describe("Pocket voice settings", () => { page, }) => { await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "voice"); const card = page.getByTestId("settings-voice"); @@ -77,7 +77,7 @@ test.describe("Pocket voice settings", () => { voicePreferences: ["pocket:eve"], }, }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "voice"); const card = page.getByTestId("settings-voice"); @@ -121,7 +121,7 @@ test.describe("Pocket voice settings", () => { page, }) => { await installMockBridge(page); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "voice"); await page.getByTestId("pocket-voice-import").click(); @@ -172,7 +172,7 @@ test.describe("Pocket voice settings", () => { page, }) => { await installMockBridge(page, { pocketVoiceImportResult: "cancel" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "voice"); await expect(page.getByTestId("pocket-voice-selector")).toContainText( @@ -197,7 +197,7 @@ test.describe("Pocket voice settings", () => { page, }) => { await installMockBridge(page, { pocketVoiceImportResult: "invalid" }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openSettings(page, "voice"); await page.getByTestId("pocket-voice-import").click(); diff --git a/desktop/tests/e2e/warm-switch-markdown.perf.ts b/desktop/tests/e2e/warm-switch-markdown.perf.ts index ae1cf6ad3d..82401278b6 100644 --- a/desktop/tests/e2e/warm-switch-markdown.perf.ts +++ b/desktop/tests/e2e/warm-switch-markdown.perf.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -211,7 +211,7 @@ test("MEASURE: warm channel-switch cost (plain 300-row + markdown-heavy)", async }) => { test.setTimeout(300_000); await installMockBridge(page); - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.waitForFunction( () => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function", ); diff --git a/desktop/tests/e2e/welcome-agent-modal-screenshots.spec.ts b/desktop/tests/e2e/welcome-agent-modal-screenshots.spec.ts index 0c506da11a..23e61c223e 100644 --- a/desktop/tests/e2e/welcome-agent-modal-screenshots.spec.ts +++ b/desktop/tests/e2e/welcome-agent-modal-screenshots.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "../helpers/test"; +import { expect, test, type Page, bootstrapE2ePage } from "../helpers/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, openCreateChannelDialog } from "../helpers/bridge"; @@ -20,7 +20,7 @@ const editorPersona = { }; async function openChannel(page: Page, name: string) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId(`channel-${name}`).click(); await expect(page.getByTestId("chat-title")).toHaveText(name); } @@ -63,7 +63,7 @@ test.describe("welcome and channel agent entry points", () => { }, ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill("Welcome"); await page @@ -106,7 +106,7 @@ test.describe("welcome and channel agent entry points", () => { }, ], }); - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await openCreateChannelDialog(page); await page.getByTestId("create-channel-name").fill("Welcome"); await page.getByTestId("create-channel-permissions").click(); diff --git a/desktop/tests/e2e/where-to-run-config.spec.ts b/desktop/tests/e2e/where-to-run-config.spec.ts index e8d87ae781..eaed57aa2d 100644 --- a/desktop/tests/e2e/where-to-run-config.spec.ts +++ b/desktop/tests/e2e/where-to-run-config.spec.ts @@ -22,7 +22,7 @@ * after the probe resolves; it is pinned at the unit level in * whereToRunIntent.test.mjs (applyProbeResult). */ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -89,7 +89,7 @@ async function selectRunOnOption( /** Open Advanced in the create-agent dialog and select the mocked provider. */ async function openCreateDialogOnProvider(page: Page) { - await page.goto("/", { waitUntil: "domcontentloaded" }); + await bootstrapE2ePage(page, "/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); const dialog = page.getByTestId("persona-dialog"); diff --git a/desktop/tests/e2e/workflows.spec.ts b/desktop/tests/e2e/workflows.spec.ts index 39169798f3..c641d146f6 100644 --- a/desktop/tests/e2e/workflows.spec.ts +++ b/desktop/tests/e2e/workflows.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; import { installMockBridge } from "../helpers/bridge"; @@ -7,7 +7,7 @@ test.beforeEach(async ({ page }) => { }); async function navigateToWorkflows(page: import("@playwright/test").Page) { - await page.goto("/"); + await bootstrapE2ePage(page, "/"); await page.getByTestId("open-workflows-view").click(); await expect(page).toHaveURL(/#\/workflows$/); await expect(page.getByTestId("workflows-view")).toBeVisible(); diff --git a/desktop/tests/helpers/bootstrap.ts b/desktop/tests/helpers/bootstrap.ts index e5f7d6711d..0eb8a81578 100644 --- a/desktop/tests/helpers/bootstrap.ts +++ b/desktop/tests/helpers/bootstrap.ts @@ -17,18 +17,22 @@ type BootstrapE2ePageOptions = { */ export async function bootstrapE2ePage( page: Page, - { + pathOrOptions: string | BootstrapE2ePageOptions = {}, + gotoOptions?: Parameters[1], +): Promise { + const { path = "/", beforeNavigate, expectedOrigin = E2E_APP_ORIGIN, - }: BootstrapE2ePageOptions = {}, -): Promise { + } = typeof pathOrOptions === "string" + ? { path: pathOrOptions } + : pathOrOptions; await beforeNavigate?.(); const target = new URL(path, expectedOrigin).toString(); let response: Awaited>; try { - response = await page.goto(target); + response = await page.goto(target, gotoOptions); } catch (error) { throw bootstrapFailure(expectedOrigin, page.url(), error); } diff --git a/desktop/tests/helpers/test.ts b/desktop/tests/helpers/test.ts index f824239807..8c6487a997 100644 --- a/desktop/tests/helpers/test.ts +++ b/desktop/tests/helpers/test.ts @@ -1,40 +1,13 @@ import { expect, test as base } from "@playwright/test"; -import { E2E_APP_ORIGIN, bootstrapE2ePage } from "./bootstrap"; - export type * from "@playwright/test"; -type E2eFixtures = { - automaticE2eBootstrap: undefined; - /** - * Set false only when a test must inspect or configure the page before its - * first app navigation. - */ - e2eBootstrap: boolean; -}; +export { bootstrapE2ePage } from "./bootstrap"; -const test = base.extend({ - e2eBootstrap: [true, { option: true }], - automaticE2eBootstrap: [ - async ({ page, baseURL, e2eBootstrap }, use) => { - if (e2eBootstrap) { - if (!baseURL) { - throw new Error( - "E2E app navigation requires Playwright use.baseURL.", - ); - } - const expectedOrigin = new URL(baseURL).origin; - if (expectedOrigin !== E2E_APP_ORIGIN) { - throw new Error( - `E2E app origin mismatch: Playwright baseURL resolves to ${expectedOrigin}, expected ${E2E_APP_ORIGIN}.`, - ); - } - await bootstrapE2ePage(page, { expectedOrigin }); - } - await use(); - }, - { auto: true }, - ], -}); +/** + * Required E2E entrypoint. Register `bootstrapE2ePage(page)` in a `beforeEach` + * after any bridge, route, or init-script setup and before app interaction. + */ +const test = base; export { expect, test }; diff --git a/web/package.json b/web/package.json index d932a612c8..7439f2a96f 100644 --- a/web/package.json +++ b/web/package.json @@ -10,11 +10,12 @@ "check:file-sizes": "node ./scripts/check-file-sizes.mjs", "check:pubkey-truncation": "node ./scripts/check-pubkey-truncation.mjs", "lint": "biome lint .", - "check": "biome check . && pnpm check:pubkey-truncation", + "check": "biome check . && pnpm check:e2e-bootstrap && pnpm check:pubkey-truncation", "format": "biome format --write .", "preview": "vite preview", "test:e2e": "pnpm build && playwright test", - "test:e2e:smoke": "pnpm build && playwright test --project=smoke" + "test:e2e:smoke": "pnpm build && playwright test --project=smoke", + "check:e2e-bootstrap": "node ./scripts/check-e2e-bootstrap.mjs" }, "dependencies": { "@fontsource-variable/inter": "^5.2.8", diff --git a/web/scripts/check-e2e-bootstrap.mjs b/web/scripts/check-e2e-bootstrap.mjs new file mode 100644 index 0000000000..47329962bd --- /dev/null +++ b/web/scripts/check-e2e-bootstrap.mjs @@ -0,0 +1,24 @@ +import fs from "node:fs"; +import path from "node:path"; + +const projectRoot = path.resolve(import.meta.dirname, ".."); +const specs = fs + .readdirSync(path.join(projectRoot, "tests/e2e")) + .filter((name) => /\.(spec|perf)\.ts$/.test(name)); +const exempt = new Set(["agents-everywhere.live.spec.ts"]); +const violations = []; +for (const name of specs) { + if (exempt.has(name)) continue; + const source = fs.readFileSync( + path.join(projectRoot, "tests/e2e", name), + "utf8", + ); + if (!source.includes('from "../helpers/test"')) continue; + if (!/await\s+bootstrapE2ePage\(/.test(source)) violations.push(name); +} +if (violations.length) { + console.error( + `E2E specs must register bootstrapE2ePage after their setup:\n${violations.join("\n")}`, + ); + process.exit(1); +} diff --git a/web/tests/e2e/bootstrap.spec.ts b/web/tests/e2e/bootstrap.spec.ts index fc08e4740e..00bbdd889c 100644 --- a/web/tests/e2e/bootstrap.spec.ts +++ b/web/tests/e2e/bootstrap.spec.ts @@ -1,20 +1,20 @@ -import { expect, test } from "../helpers/test"; -import { E2E_APP_ORIGIN, bootstrapE2ePage } from "../helpers/bootstrap"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; +import { E2E_APP_ORIGIN } from "../helpers/bootstrap"; -test("automatically starts each test at the app origin", async ({ page }) => { +test.beforeEach(async ({ page }) => { + await bootstrapE2ePage(page); +}); + +test("starts each test at the app origin after explicit bootstrap", async ({ + page, +}) => { expect(new URL(page.url()).origin).toBe(E2E_APP_ORIGIN); }); -test.describe("pre-navigation setup", () => { - test.use({ e2eBootstrap: false }); - - test("reports a failed initial navigation at its source", async ({ - page, - }) => { - await expect( - bootstrapE2ePage(page, { expectedOrigin: "http://127.0.0.1:1" }), - ).rejects.toThrow( - /E2E app navigation did not commit to http:\/\/127\.0\.0\.1:1 \(current URL: about:blank\).*Run pnpm test:e2e:smoke or run pnpm build and check the preview server/, - ); - }); +test("reports a failed initial navigation at its source", async ({ page }) => { + await expect( + bootstrapE2ePage(page, { expectedOrigin: "http://127.0.0.1:1" }), + ).rejects.toThrow( + /E2E app navigation did not commit to http:\/\/127\.0\.0\.1:1 \(current URL: http:\/\/127\.0\.0\.1:4173\/\).*Run pnpm test:e2e:smoke or run pnpm build and check the preview server/, + ); }); diff --git a/web/tests/e2e/smoke.spec.ts b/web/tests/e2e/smoke.spec.ts index 09722dd278..ed45e3fdc3 100644 --- a/web/tests/e2e/smoke.spec.ts +++ b/web/tests/e2e/smoke.spec.ts @@ -1,5 +1,5 @@ import { createHash } from "node:crypto"; -import { expect, test } from "../helpers/test"; +import { expect, test, bootstrapE2ePage } from "../helpers/test"; test("home page loads with Buzz branding", async ({ page }) => { await expect( @@ -64,7 +64,7 @@ test("invite requires age and legal consent before opening Buzz", async ({ ]), }); }); - await page.goto("/invite/demo-code"); + await bootstrapE2ePage(page, "/invite/demo-code"); await expect( page.getByRole("link", { name: "Download it now" }), @@ -196,7 +196,7 @@ test("invite can enroll a NIP-07 identity for browser access", async ({ }); await installNip07Extension(); - await page.goto("/invite/browser-code"); + await bootstrapE2ePage(page, "/invite/browser-code"); await page.getByRole("button", { name: "Join in browser" }).click(); await expect(page).toHaveURL("/"); expect(claimObserved).toBe(true); @@ -228,7 +228,7 @@ test("invite asks Safari users to choose their Mac download", async ({ await route.fulfill({ status: 500 }); }); - await page.goto("/invite/demo-code"); + await bootstrapE2ePage(page, "/invite/demo-code"); const download = page.getByRole("link", { name: "Download it now" }); await expect(download).toHaveAttribute("aria-haspopup", "dialog"); await download.click(); @@ -340,7 +340,7 @@ test("invite download falls back for mobile and non-desktop devices", async ({ }); }); - await page.goto("/invite/demo-code"); + await bootstrapE2ePage(page, "/invite/demo-code"); await expect( page.getByRole("link", { name: "Download it now" }), device.name, diff --git a/web/tests/helpers/bootstrap.ts b/web/tests/helpers/bootstrap.ts index a335024509..9b96ae6cc3 100644 --- a/web/tests/helpers/bootstrap.ts +++ b/web/tests/helpers/bootstrap.ts @@ -17,18 +17,22 @@ type BootstrapE2ePageOptions = { */ export async function bootstrapE2ePage( page: Page, - { + pathOrOptions: string | BootstrapE2ePageOptions = {}, + gotoOptions?: Parameters[1], +): Promise { + const { path = "/", beforeNavigate, expectedOrigin = E2E_APP_ORIGIN, - }: BootstrapE2ePageOptions = {}, -): Promise { + } = typeof pathOrOptions === "string" + ? { path: pathOrOptions } + : pathOrOptions; await beforeNavigate?.(); const target = new URL(path, expectedOrigin).toString(); let response: Awaited>; try { - response = await page.goto(target); + response = await page.goto(target, gotoOptions); } catch (error) { throw bootstrapFailure(expectedOrigin, page.url(), error); } diff --git a/web/tests/helpers/test.ts b/web/tests/helpers/test.ts index f824239807..8c6487a997 100644 --- a/web/tests/helpers/test.ts +++ b/web/tests/helpers/test.ts @@ -1,40 +1,13 @@ import { expect, test as base } from "@playwright/test"; -import { E2E_APP_ORIGIN, bootstrapE2ePage } from "./bootstrap"; - export type * from "@playwright/test"; -type E2eFixtures = { - automaticE2eBootstrap: undefined; - /** - * Set false only when a test must inspect or configure the page before its - * first app navigation. - */ - e2eBootstrap: boolean; -}; +export { bootstrapE2ePage } from "./bootstrap"; -const test = base.extend({ - e2eBootstrap: [true, { option: true }], - automaticE2eBootstrap: [ - async ({ page, baseURL, e2eBootstrap }, use) => { - if (e2eBootstrap) { - if (!baseURL) { - throw new Error( - "E2E app navigation requires Playwright use.baseURL.", - ); - } - const expectedOrigin = new URL(baseURL).origin; - if (expectedOrigin !== E2E_APP_ORIGIN) { - throw new Error( - `E2E app origin mismatch: Playwright baseURL resolves to ${expectedOrigin}, expected ${E2E_APP_ORIGIN}.`, - ); - } - await bootstrapE2ePage(page, { expectedOrigin }); - } - await use(); - }, - { auto: true }, - ], -}); +/** + * Required E2E entrypoint. Register `bootstrapE2ePage(page)` in a `beforeEach` + * after any bridge, route, or init-script setup and before app interaction. + */ +const test = base; export { expect, test };