Files
myeasycms-v2/apps/e2e/tests/team-accounts/team-accounts.spec.ts
Giancarlo Buomprisco d5dc6f2528 2.23.0: Enforce Policies API for invitations and creating accounts; added WeakPassword handling; Fix dialog open/closed states (#439)
* chore: bump version to 2.22.1 and update dependencies

- Updated application version from 2.22.0 to 2.22.1 in package.json.
- Updated various dependencies including @marsidev/react-turnstile to 1.4.1, @stripe/react-stripe-js to 5.4.1, @stripe/stripe-js to 8.6.1, and react-hook-form to 7.70.0.
- Adjusted lucide-react version to be referenced from the catalog across multiple package.json files.
- Enhanced consistency in pnpm-lock.yaml and pnpm-workspace.yaml with updated package versions.

* chore: bump version to 2.23.0 and update dependencies

- Updated application version from 2.22.1 to 2.23.0 in package.json.
- Upgraded turbo dependency from 2.7.1 to 2.7.3 in package.json and pnpm-lock.yaml.
- Enhanced end-to-end testing documentation in AGENTS.md and CLAUDE.md with instructions for running tests.
- Updated AuthPageObject to use a new secret for user creation in auth.po.ts.
- Refactored team ownership transfer and member role update dialogs to close on success.
- Improved error handling for weak passwords in AuthErrorAlert component.
- Adjusted database schemas and tests to reflect changes in invitation policies and role management.
2026-01-07 17:00:11 +01:00

340 lines
10 KiB
TypeScript

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`);
});
});