Skip to content

fix(e2e): mock Discord OAuth in E2E tests (PP-e20)#1292

Closed
timothyfroehlich wants to merge 4 commits intomainfrom
fix/e2e-discord-oauth-mock-PP-e20
Closed

fix(e2e): mock Discord OAuth in E2E tests (PP-e20)#1292
timothyfroehlich wants to merge 4 commits intomainfrom
fix/e2e-discord-oauth-mock-PP-e20

Conversation

@timothyfroehlich
Copy link
Copy Markdown
Owner

Why

Two E2E tests in oauth-connected-accounts.spec.ts were timing out waiting for discord.com redirects 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.ts intercepts the Supabase /authorize endpoint via page.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.ts adds linkUserDiscordIdentity / unlinkUserDiscordIdentity helpers for direct manipulation of auth.identities (and the discord_user_id mirror) — used in the authenticated test setup.
  • e2e/support/actions.ts wraps the logout() helper's menu open+assert in expect.toPass() to fix the Radix portal mount timing flake (co-fix for PP-e20).
  • oauth-connected-accounts.spec.ts updated to use the mock pattern; the test.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 plan

  • CI passes E2E Full Tests on oauth-connected-accounts.spec.ts (anonymous Discord button)
  • CI passes E2E Full Tests on oauth-connected-accounts.spec.ts (authenticated Connect Discord)
  • No outbound network calls to discord.com in test runs

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>
Copilot AI review requested due to automatic review settings May 5, 2026 20:54
@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pin-point Ready Ready Preview, Comment May 5, 2026 9:42pm

@supabase
Copy link
Copy Markdown

supabase Bot commented May 5, 2026

This pull request has been ignored for the connected project udhesuizjsgxfeotqybn due to reaching the limit of concurrent preview branches.
Go to Project Integrations Settings ↗︎ if you wish to update this limit.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/authorize navigation.
  • Added Supabase admin helpers to link/unlink a Discord identity directly in auth.identities (plus the user_profiles.discord_user_id mirror).
  • 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.

Comment thread e2e/support/oauth-mocks.ts Outdated
Comment thread e2e/support/supabase-admin.ts
Comment thread e2e/support/supabase-admin.ts
Comment thread e2e/support/supabase-admin.ts Outdated
Comment thread e2e/full/oauth-connected-accounts.spec.ts Outdated
Comment thread e2e/full/oauth-connected-accounts.spec.ts Outdated
Comment thread e2e/full/oauth-connected-accounts.spec.ts Outdated
Comment thread e2e/support/actions.ts
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Comment on lines 73 to 93
@@ -79,9 +93,25 @@ test.describe("OAuth + Connected Accounts", () => {
await expect(connectBtn).toBeVisible();
Comment on lines +52 to +81
// 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}`);

Comment on lines +31 to +37
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"}`);

Comment on lines +42 to +60
// 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,
},
});
Comment on lines +14 to +40
* 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>
@timothyfroehlich
Copy link
Copy Markdown
Owner Author

Closed in favor of #1293 (DB-manipulation redesign — route interception couldn't work for linkIdentity which fails server-side). —Claude

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants