import { test, expect } from '@playwright/test'; import { createClient } from '@supabase/supabase-js'; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://127.0.0.1:54321'; const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || ''; const supabase = createClient(supabaseUrl, supabaseKey); const ownerEmail = `owner_${Date.now()}@example.com`; const ownerPassword = 'testpassword123'; const inviteeEmail = `invitee_${Date.now()}@example.com`; const inviteePassword = 'testpassword123'; test.describe('Team Invitation Flow', () => { test('Should process creating, revoking and accepting invitations', async ({ browser }) => { // We use isolated browser contexts to simulate two users const ownerContext = await browser.newContext(); const ownerPage = await ownerContext.newPage(); // Step 1: Sign up owner await ownerPage.goto('/auth/sign-up'); await ownerPage.fill('input[type="email"]', ownerEmail); await ownerPage.fill('input[id="password"]', ownerPassword); await ownerPage.fill('input[id="repeat-password"]', ownerPassword); await ownerPage.click('button[type="submit"]'); // NEW: Auto-confirm the owner email using Service Role Key await ownerPage.waitForTimeout(1000); const { data: { users: ownerUsers } } = await supabase.auth.admin.listUsers(); const ownerUser = ownerUsers.find(u => u.email === ownerEmail); if (ownerUser) { await supabase.auth.admin.updateUserById(ownerUser.id, { email_confirm: true }); } // Wait for the auth redirect await ownerPage.waitForURL('**/auth/sign-up-success**', { timeout: 10000 }).catch(() => null); // Some implementations redirect to sign-up-success then require manual login if (ownerPage.url().includes('sign-up-success')) { await ownerPage.goto('/auth/login'); await ownerPage.fill('input[type="email"]', ownerEmail); await ownerPage.fill('input[type="password"]', ownerPassword); await ownerPage.click('button[type="submit"]'); } // Onboarding await ownerPage.waitForURL('**/onboarding'); await ownerPage.fill('input[name="name"]', 'Test E2E Organization'); await ownerPage.click('button[type="submit"]'); // Dashboard await ownerPage.waitForURL('**/dashboard'); // Step 2: Go to Team Settings await ownerPage.goto('/dashboard/settings/team'); // Send Invite to be revoked await ownerPage.fill('input[name="email"]', 'revokeme@example.com'); await ownerPage.click('button:has-text("Send Invite")'); // Verify it appears in pending await expect(ownerPage.locator(`text=revokeme@example.com`)).toBeVisible(); // Step 3: Revoke Invite await ownerPage.locator('li').filter({ hasText: 'revokeme@example.com' }).locator('button[title="Revoke Invitation"]').click(); // Verify it's gone await expect(ownerPage.locator(`text=revokeme@example.com`)).not.toBeVisible(); // Step 4: Create real invite await ownerPage.fill('input[name="email"]', inviteeEmail); await ownerPage.click('button:has-text("Send Invite")'); await expect(ownerPage.locator(`text=${inviteeEmail}`)).toBeVisible(); // Fetch the token directly from database let token = ''; // Wait to ensure database has processed the insert await ownerPage.waitForTimeout(2000); const { data: invitations } = await supabase .from('invitations') .select('token') .eq('email', inviteeEmail) .eq('status', 'pending'); token = invitations?.[0]?.token; expect(token).toBeTruthy(); // Step 5: Invitee flow const inviteeContext = await browser.newContext(); const inviteePage = await inviteeContext.newPage(); // Access the invite link await inviteePage.goto(`/api/invite/accept?token=${token}`); // Should be redirected to sign up (if not logged in) await inviteePage.waitForURL('**/auth/sign-up**'); // Verify email is pre-filled and locked const emailInput = inviteePage.locator('input[type="email"]'); await expect(emailInput).toHaveValue(inviteeEmail); await expect(emailInput).toHaveAttribute('readOnly', ''); // Sign up invitee await inviteePage.fill('input[id="password"]', inviteePassword); await inviteePage.fill('input[id="repeat-password"]', inviteePassword); await inviteePage.click('button[type="submit"]'); // NEW: Auto-confirm the invitee email using Service Role Key await inviteePage.waitForTimeout(1000); const { data: { users: inviteeUsers } } = await supabase.auth.admin.listUsers(); const inviteeUser = inviteeUsers.find(u => u.email === inviteeEmail); if (inviteeUser) { await supabase.auth.admin.updateUserById(inviteeUser.id, { email_confirm: true }); } // Login if needed if (inviteePage.url().includes('sign-up-success')) { await inviteePage.goto('/auth/login'); await inviteePage.fill('input[type="email"]', inviteeEmail); await inviteePage.fill('input[type="password"]', inviteePassword); await inviteePage.click('button[type="submit"]'); } // Should automatically skip onboarding and land in dashboard await inviteePage.waitForURL('**/dashboard'); // Step 6: Verify Invitee is in Team Settings await inviteePage.goto('/dashboard/settings/team'); // Invitee should see themselves in Members list (and the owner) await expect(inviteePage.locator('h2:has-text("Members")')).toBeVisible(); await expect(inviteePage.locator(`li`, { hasText: inviteeEmail })).toBeVisible(); await expect(inviteePage.locator(`li`, { hasText: ownerEmail })).toBeVisible(); }); });