import { Page, expect, test } from '@playwright/test'; import { InvitationsPageObject } from '../invitations/invitations.po'; import { TeamAccountsPageObject } from './team-accounts.po'; // Helper function to set up a team with a member async function setupTeamWithMember(page: Page, memberRole = 'member') { // Setup invitations page object const invitations = new InvitationsPageObject(page); const teamAccounts = invitations.teamAccounts; // Setup team with owner const { email: ownerEmail, slug } = await invitations.setup(); // Navigate to members page await invitations.navigateToMembers(); // Create a new member email and invite them with the specified role const memberEmail = invitations.auth.createRandomEmail(); const invites = [ { email: memberEmail, role: memberRole, }, ]; await invitations.openInviteForm(); await invitations.inviteMembers(invites); // Verify the invitation was sent await expect(invitations.getInvitations()).toHaveCount(1); // Sign out the current user await page.context().clearCookies(); // Sign up with the new member email and accept the invitation await invitations.auth.visitConfirmEmailLink(memberEmail); await invitations.acceptInvitation(); await invitations.teamAccounts.openAccountsSelector(); await expect(invitations.teamAccounts.getTeams()).toHaveCount(1); // Sign out and sign back in as the original owner await page.context().clearCookies(); await page.goto('/auth/sign-in'); await invitations.auth.loginAsUser({ email: ownerEmail, next: '/home', }); // Navigate to the team members page await page.goto(`/home/${slug}/members`); return { invitations, teamAccounts, ownerEmail, memberEmail, slug }; } test.describe('Team Accounts', () => { test.beforeEach(async ({ page }) => { const teamAccounts = new TeamAccountsPageObject(page); await teamAccounts.setup(); }); test('user can update their team name (and slug)', async ({ page }) => { const teamAccounts = new TeamAccountsPageObject(page); const { teamName, slug } = teamAccounts.createTeamName(); await teamAccounts.goToSettings(); const request = teamAccounts.updateName(teamName, slug); // the slug should be updated to match the new team name const newUrl = page.waitForURL(`**/home/${slug}/settings`); await Promise.all([request, newUrl]); await teamAccounts.openAccountsSelector(); await expect(teamAccounts.getTeamFromSelector(teamName)).toBeVisible(); }); test('cannot create a Team account using reserved names', async ({ page, }) => { const teamAccounts = new TeamAccountsPageObject(page); await teamAccounts.createTeam(); await teamAccounts.openAccountsSelector(); await page.click('[data-test="create-team-account-trigger"]'); await teamAccounts.tryCreateTeam('billing'); await expect( page.getByText('This name is reserved. Please choose a different one.'), ).toBeVisible(); await teamAccounts.tryCreateTeam('settings'); await expect( page.getByText('This name is reserved. Please choose a different one.'), ).toBeVisible(); function expectError() { return expect( page.getByText( 'This name cannot contain special characters. Please choose a different one.', ), ).toBeVisible(); } await teamAccounts.tryCreateTeam('Test-Name#'); await expectError(); await teamAccounts.tryCreateTeam('Test,Name'); await expectError(); await teamAccounts.tryCreateTeam('Test Name/'); await expectError(); await teamAccounts.tryCreateTeam('Test Name\\'); await expectError(); await teamAccounts.tryCreateTeam('Test Name:'); await expectError(); await teamAccounts.tryCreateTeam('Test Name;'); await expectError(); await teamAccounts.tryCreateTeam('Test Name='); await expectError(); await teamAccounts.tryCreateTeam('Test Name>'); await expectError(); await teamAccounts.tryCreateTeam('Test Name<'); await expectError(); await teamAccounts.tryCreateTeam('Test Name?'); await expectError(); await teamAccounts.tryCreateTeam('Test Name@'); await expectError(); await teamAccounts.tryCreateTeam('Test Name^'); await expectError(); await teamAccounts.tryCreateTeam('Test Name&'); await expectError(); await teamAccounts.tryCreateTeam('Test Name*'); await expectError(); await teamAccounts.tryCreateTeam('Test Name('); await expectError(); await teamAccounts.tryCreateTeam('Test Name)'); await expectError(); await teamAccounts.tryCreateTeam('Test Name+'); await expectError(); await teamAccounts.tryCreateTeam('Test Name%'); await expectError(); await teamAccounts.tryCreateTeam('Test Name$'); await expectError(); await teamAccounts.tryCreateTeam('Test Name['); await expectError(); await teamAccounts.tryCreateTeam('Test Name]'); await expectError(); }); test('cannot create a Team account using non-latin characters', async ({ page, }) => { const teamAccounts = new TeamAccountsPageObject(page); await teamAccounts.createTeam(); await teamAccounts.openAccountsSelector(); await page.click('[data-test="create-team-account-trigger"]'); function expectNonLatinError() { return expect( page.getByText( 'This name can only contain Latin characters (a-z), numbers, spaces, and hyphens.', ), ).toBeVisible(); } // Test Cyrillic characters await teamAccounts.tryCreateTeam('Тест Команда'); await expectNonLatinError(); // Test Chinese characters await teamAccounts.tryCreateTeam('测试团队'); await expectNonLatinError(); // Test Japanese characters await teamAccounts.tryCreateTeam('テストチーム'); await expectNonLatinError(); // Test Arabic characters await teamAccounts.tryCreateTeam('فريق اختبار'); await expectNonLatinError(); // Test mixed Latin and non-Latin await teamAccounts.tryCreateTeam('Test Команда'); await expectNonLatinError(); // Test emoji await teamAccounts.tryCreateTeam('Test Team 🚀'); await expectNonLatinError(); // Ensure valid Latin names still work (should NOT show error) await teamAccounts.tryCreateTeam('Valid Team Name 123'); await expect( page.getByText( 'This name can only contain Latin characters (a-z), numbers, spaces, and hyphens.', ), ).not.toBeVisible(); }); }); test.describe('Team Account Deletion', () => { test('user can delete their team account', async ({ page }) => { const teamAccounts = new TeamAccountsPageObject(page); const params = teamAccounts.createTeamName(); const { email } = await teamAccounts.setup(params); await teamAccounts.goToSettings(); await teamAccounts.deleteAccount(email); await teamAccounts.openAccountsSelector(); await expect( teamAccounts.getTeamFromSelector(params.teamName), ).not.toBeVisible(); }); }); test.describe('Team Member Role Management', () => { test("owner can update a team member's role", async ({ page }) => { // Setup team with a regular member const { teamAccounts, memberEmail } = await setupTeamWithMember(page); // Get the current role badge text const memberRow = page.getByRole('row', { name: memberEmail }); const initialRoleBadge = memberRow.locator( '[data-test="member-role-badge"]', ); await expect(initialRoleBadge).toHaveText('Member'); // Update the member's role to admin await teamAccounts.updateMemberRole(memberEmail, 'owner'); await expect( page .getByRole('row', { name: memberEmail }) .locator('[data-test="member-role-badge"]'), ).toHaveText('Owner'); }); }); test.describe('Team Ownership Transfer', () => { test('owner can transfer ownership to another team member', async ({ page, }) => { // Setup team with an owner member (required for ownership transfer) const { teamAccounts, ownerEmail, memberEmail } = await setupTeamWithMember( page, 'owner', ); // Transfer ownership to the member await teamAccounts.transferOwnership(memberEmail, ownerEmail); // Verify the transfer was successful by checking if the primary owner badge // is now on the new owner's row const memberRow = page.getByRole('row', { name: memberEmail }); // Check for the primary owner badge on the member's row await expect(memberRow.locator('text=Primary Owner')).toBeVisible(); // The original owner should no longer have the primary owner badge const ownerRow = page.getByRole('row', { name: ownerEmail.split('@')[0] }); await expect(ownerRow.locator('text=Primary Owner')).not.toBeVisible(); }); }); test.describe('Team Account Security', () => { test('unauthorized user cannot access team account', async ({ page, browser, }) => { // 1. Create a team account with User A const teamAccounts = new TeamAccountsPageObject(page); const params = teamAccounts.createTeamName(); // Setup User A and create team await teamAccounts.setup(params); // Store team slug for later use const teamSlug = params.slug; // 2. Sign out User A await page.context().clearCookies(); // 3. Create a new context for User B (to have clean cookies/session) const userBContext = await browser.newContext(); const userBPage = await userBContext.newPage(); const userBTeamAccounts = new TeamAccountsPageObject(userBPage); // Sign up with User B await userBPage.goto('/auth/sign-up'); const emailB = userBTeamAccounts.auth.createRandomEmail(); await userBTeamAccounts.auth.signUp({ email: emailB, password: 'password', repeatPassword: 'password', }); await userBTeamAccounts.auth.visitConfirmEmailLink(emailB); // 4. Attempt to access the team page with User B await userBPage.goto(`/home/${teamSlug}`); // Check that we're not on the team page anymore (should redirect) await expect(userBPage).toHaveURL(`/home`); }); });