fix(e2e): mock Discord OAuth in E2E tests (PP-e20)#1292
fix(e2e): mock Discord OAuth in E2E tests (PP-e20)#1292timothyfroehlich wants to merge 4 commits intomainfrom
Conversation
Replaces real-Discord redirect with Playwright route interception for the Supabase authorize endpoint. No test reaches an external OAuth provider — this is now policy for all OAuth tests. Adds: - e2e/support/oauth-mocks.ts: route interception + fake-redirect helpers - e2e/support/supabase-admin.ts helpers (linkUserDiscordIdentity, unlinkUserDiscordIdentity) for direct identity table manipulation - e2e/support/actions.ts: logout() retry via expect.toPass() for Radix portal mount flake (PP-e20 co-fix) Removes test.fixme calls from oauth-connected-accounts.spec.ts since the mock is the actual fix (was silenced in PR #1290 pending this work). Tracking: PP-e20. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
There was a problem hiding this comment.
Pull request overview
Mocks Discord OAuth in Playwright E2E to prevent timeouts/flakes caused by Supabase’s local OAuth provider not being configured with real Discord credentials, and removes the PP-e20 test.fixme() skips by making the specs runnable offline.
Changes:
- Added a Playwright network-level OAuth mock helper that intercepts Supabase
/auth/v1/authorizenavigation. - Added Supabase admin helpers to link/unlink a Discord identity directly in
auth.identities(plus theuser_profiles.discord_user_idmirror). - Hardened the
logout()E2E helper against Radix dropdown portal/hydration timing flake and updated the connected-accounts spec to use the mock.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| e2e/support/supabase-admin.ts | Adds DB helpers to link/unlink Discord identities for mocked OAuth flows. |
| e2e/support/oauth-mocks.ts | New Playwright route-based OAuth interception helper. |
| e2e/support/actions.ts | Wraps logout dropdown open/assert in a retry to reduce CI flake. |
| e2e/full/oauth-connected-accounts.spec.ts | Removes PP-e20 fixmes and switches tests to the new OAuth mock + identity seeding approach. |
The PP-e20 commit replaced setUserDiscordId with linkUserDiscordIdentity / unlinkUserDiscordIdentity, but discord-dm-preferences.spec.ts still imports the old setUserDiscordId name. Re-add it as a thin supabase-js wrapper (just updates user_profiles.discord_user_id) alongside the new auth.identities helpers — both serve different test needs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- oauth-mocks.ts: build absolute Location URL for 302 redirect (relative
URL would resolve against Supabase origin, not Next.js app origin)
- oauth-connected-accounts.spec.ts: rename tests to describe mocked behavior
(not real Discord redirect); remove inline postgres query + non-null assert;
use user-unique mock discord ID (mock_discord_${userId}) to avoid ON CONFLICT
skipping wrong user's identity
- supabase-admin.ts: change ON CONFLICT DO NOTHING to DO UPDATE so the
identity row is always owned by the intended user; throw in
unlinkUserDiscordIdentity when env var is missing (consistent with link)
- actions.ts: skip re-click in toPass loop when signOutItem is already
visible (Radix toggle closes on second click)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| @@ -79,9 +93,25 @@ test.describe("OAuth + Connected Accounts", () => { | |||
| await expect(connectBtn).toBeVisible(); | |||
| // We do this BEFORE clicking so the server-rendered component sees the correct | ||
| // state on the page load that follows the mock redirect. | ||
| await linkUserDiscordIdentity(userId, `mock_discord_${userId}`); | ||
|
|
| const pageUrl = page.url(); | ||
| const appOrigin = | ||
| pageUrl && pageUrl !== "about:blank" | ||
| ? new URL(pageUrl).origin | ||
| : (process.env.NEXT_PUBLIC_BASE_URL ?? | ||
| `http://localhost:${process.env.PORT ?? "3000"}`); | ||
|
|
| // When the Next.js Server Action completes, it redirects the browser to | ||
| // the Supabase Auth API (/auth/v1/authorize?provider=...). | ||
| // We intercept this navigation. Since this is a standard browser navigation | ||
| // outside the Next.js app, we don't break the client-side router. | ||
| await page.route( | ||
| `**/auth/v1/authorize?provider=${provider}**`, | ||
| async (route) => { | ||
| console.log( | ||
| `[OAuth Mock] Intercepted Supabase Authorize: ${route.request().url()}` | ||
| ); | ||
|
|
||
| // Fulfill with an absolute redirect back to the app's target URL, | ||
| // simulating the completion of the OAuth flow and the callback redirect. | ||
| await route.fulfill({ | ||
| status: 302, | ||
| headers: { | ||
| Location: absoluteTargetUrl, | ||
| }, | ||
| }); |
| * The OAuth flow is mocked via Playwright route interception — no test reaches | ||
| * a real external provider. See e2e/support/oauth-mocks.ts. | ||
| */ | ||
|
|
||
| import { test, expect } from "@playwright/test"; | ||
| import { STORAGE_STATE } from "../support/auth-state"; | ||
| import { setupOAuthMock } from "../support/oauth-mocks"; | ||
| import { | ||
| linkUserDiscordIdentity, | ||
| unlinkUserDiscordIdentity, | ||
| getUserIdByEmail, | ||
| } from "../support/supabase-admin"; | ||
| import { TEST_USERS } from "../support/constants"; | ||
|
|
||
| test.describe("OAuth + Connected Accounts", () => { | ||
| test.skip( | ||
| !process.env.DISCORD_CLIENT_ID, | ||
| "Requires DISCORD_CLIENT_ID in test env" | ||
| ); | ||
|
|
||
| test.describe("anonymous", () => { | ||
| test.use({ storageState: { cookies: [], origins: [] } }); | ||
|
|
||
| test("Continue with Discord button on /login redirects to discord.com", async ({ | ||
| test("Continue with Discord button on /login completes mocked OAuth flow", async ({ | ||
| page, | ||
| }) => { | ||
| test.fixme( | ||
| true, | ||
| "PP-e20 — Discord OAuth not configured in test env; awaiting mock-only fix (no test should reach real Discord)" | ||
| ); | ||
| // Mock the OAuth flow so it doesn't hit real Discord or fail server-side | ||
| await setupOAuthMock(page, { | ||
| provider: "discord", | ||
| targetUrl: "/dashboard", | ||
| }); | ||
|
|
…(PP-e20) Two bugs in the mocked OAuth tests: 1. Anonymous test: redirected to /dashboard which middleware blocks for anonymous users, causing a re-redirect to /login. Fix: redirect to /login?oauth_mock=true (public, verifiable with waitForURL predicate). 2. Authenticated test: linkUserDiscordIdentity was called BEFORE page.goto, so the settings page already showed "Disconnect Discord" when we looked for "Connect Discord". Fix: call linkUserDiscordIdentity AFTER the mock redirect lands (simulating what the real OAuth callback would do), then reload so the server-rendered component reads updated DB state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Closed in favor of #1293 (DB-manipulation redesign — route interception couldn't work for linkIdentity which fails server-side). —Claude |
Why
Two E2E tests in
oauth-connected-accounts.spec.tswere timing out waiting fordiscord.comredirects that never happened — Supabase's local Discord provider isn't configured with real client_id/secret/redirect_uri. This PR mocks the OAuth flow so no test reaches Discord (or any external OAuth provider).Approach
e2e/support/oauth-mocks.tsintercepts the Supabase/authorizeendpoint viapage.route('**/auth/v1/authorize?provider=discord**')and fulfills with a fake redirect to PinPoint's auth callback with stub session params.e2e/support/supabase-admin.tsaddslinkUserDiscordIdentity/unlinkUserDiscordIdentityhelpers for direct manipulation ofauth.identities(and thediscord_user_idmirror) — used in the authenticated test setup.e2e/support/actions.tswraps thelogout()helper's menu open+assert inexpect.toPass()to fix the Radix portal mount timing flake (co-fix for PP-e20).oauth-connected-accounts.spec.tsupdated to use the mock pattern; thetest.fixme(true, "PP-e20")calls are removed since the mock IS the fix.Policy: no test reaches real OAuth providers
This applies to Discord and any future provider (Google, GitHub, etc.). Real OAuth in tests creates uncontrollable flakes (rate limits, outages, UI changes), and configuring real credentials across local/CI/branch DBs is a much larger project.
Tracking
test.fixmecalls added by chore(e2e): silence 5 broken specs to restore CI signal (PP-5ra) #1290 (skip-PR)Test plan
oauth-connected-accounts.spec.ts(anonymous Discord button)oauth-connected-accounts.spec.ts(authenticated Connect Discord)