forked from cardosofelipe/fast-next-template
Add tests for Organization Members, handling roles and pagination
- Introduced unit tests for `OrganizationMembersPage` and `OrganizationMembersTable`, covering rendering, role badges, and pagination controls. - Enhanced E2E tests with updated admin organization navigation and asserted breadcrumbs structure. - Mocked API routes for members, organizations, and sessions in E2E helpers to support dynamic test scenarios.
This commit is contained in:
@@ -172,7 +172,7 @@ test.describe('Admin Navigation', () => {
|
||||
await page.goto('/admin/organizations');
|
||||
|
||||
await expect(page).toHaveURL('/admin/organizations');
|
||||
await expect(page.locator('h1')).toContainText('Organizations');
|
||||
await expect(page.locator('h2')).toContainText('All Organizations');
|
||||
|
||||
// Breadcrumbs should show Admin > Organizations
|
||||
await expect(page.getByTestId('breadcrumb-admin')).toBeVisible();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/**
|
||||
* E2E Tests for Admin Organization Members Management
|
||||
* Tests basic navigation to organization members page
|
||||
*
|
||||
* Note: Interactive member management tests are covered by comprehensive unit tests (43 tests).
|
||||
* E2E tests focus on navigation and page structure due to backend API mock limitations.
|
||||
* Tests AddMemberDialog Select interactions (excluded from unit tests with istanbul ignore)
|
||||
* and basic navigation to organization members page
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
@@ -117,3 +115,132 @@ test.describe('Admin Organization Members - Page Structure', () => {
|
||||
await expect(icon).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Admin Organization Members - AddMemberDialog E2E Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupSuperuserMocks(page);
|
||||
await loginViaUI(page);
|
||||
await page.goto('/admin/organizations');
|
||||
await page.waitForSelector('table tbody tr', { timeout: 10000 });
|
||||
|
||||
// Navigate to members page
|
||||
const actionButton = page.getByRole('button', { name: /Actions for/i }).first();
|
||||
await actionButton.click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/admin\/organizations\/[^/]+\/members/, { timeout: 10000 }),
|
||||
page.getByText('View Members').click()
|
||||
]);
|
||||
|
||||
// Open Add Member dialog
|
||||
const addButton = page.getByRole('button', { name: /Add Member/i });
|
||||
await addButton.click();
|
||||
|
||||
// Wait for dialog to be visible
|
||||
await page.waitForSelector('[role="dialog"]', { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('should open add member dialog when clicking add member button', async ({ page }) => {
|
||||
// Dialog should be visible
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Should have dialog title
|
||||
await expect(page.getByRole('heading', { name: /Add Member/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display dialog description', async ({ page }) => {
|
||||
await expect(page.getByText(/Add a new member to this organization/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display user email select field', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
await expect(dialog.getByText('User Email')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display role select field', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
await expect(dialog.getByText('Role')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display add member and cancel buttons', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
await expect(dialog.getByRole('button', { name: /^Add Member$/i })).toBeVisible();
|
||||
await expect(dialog.getByRole('button', { name: /Cancel/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should close dialog when clicking cancel', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
const cancelButton = dialog.getByRole('button', { name: /Cancel/i });
|
||||
|
||||
await cancelButton.click();
|
||||
|
||||
// Dialog should be closed
|
||||
await expect(dialog).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should open user email select dropdown when clicked', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
|
||||
// Click user email select trigger
|
||||
const userSelect = dialog.getByRole('combobox').first();
|
||||
await userSelect.click();
|
||||
|
||||
// Dropdown should be visible with mock user options
|
||||
await expect(page.getByRole('option', { name: /test@example.com/i })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: /admin@example.com/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should select user email from dropdown', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
|
||||
// Click user email select trigger
|
||||
const userSelect = dialog.getByRole('combobox').first();
|
||||
await userSelect.click();
|
||||
|
||||
// Select first user
|
||||
await page.getByRole('option', { name: /test@example.com/i }).click();
|
||||
|
||||
// Selected value should be visible
|
||||
await expect(userSelect).toContainText('test@example.com');
|
||||
});
|
||||
|
||||
test('should open role select dropdown when clicked', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
|
||||
// Click role select trigger (second combobox)
|
||||
const roleSelects = dialog.getByRole('combobox');
|
||||
const roleSelect = roleSelects.nth(1);
|
||||
await roleSelect.click();
|
||||
|
||||
// Dropdown should show role options
|
||||
await expect(page.getByRole('option', { name: /^Owner$/i })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: /^Admin$/i })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: /^Member$/i })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: /^Guest$/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should select role from dropdown', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
|
||||
// Click role select trigger
|
||||
const roleSelects = dialog.getByRole('combobox');
|
||||
const roleSelect = roleSelects.nth(1);
|
||||
await roleSelect.click();
|
||||
|
||||
// Select admin role
|
||||
await page.getByRole('option', { name: /^Admin$/i }).click();
|
||||
|
||||
// Selected value should be visible
|
||||
await expect(roleSelect).toContainText('Admin');
|
||||
});
|
||||
|
||||
test('should have default role as Member', async ({ page }) => {
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
const roleSelects = dialog.getByRole('combobox');
|
||||
const roleSelect = roleSelects.nth(1);
|
||||
|
||||
// Default role should be Member
|
||||
await expect(roleSelect).toContainText('Member');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -342,4 +342,86 @@ export async function setupSuperuserMocks(page: Page): Promise<void> {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Mock GET /api/v1/admin/stats - Get dashboard statistics
|
||||
await page.route(`${baseURL}/api/v1/admin/stats`, async (route: Route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
total_users: 150,
|
||||
active_users: 120,
|
||||
total_organizations: 25,
|
||||
active_sessions: 45,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Mock GET /api/v1/admin/organizations/:id - Get single organization
|
||||
await page.route(`${baseURL}/api/v1/admin/organizations/*/`, async (route: Route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
// Extract org ID from URL
|
||||
const url = route.request().url();
|
||||
const orgId = url.match(/organizations\/([^/]+)/)?.[1];
|
||||
const org = MOCK_ORGANIZATIONS.find(o => o.id === orgId) || MOCK_ORGANIZATIONS[0];
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(org),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Mock GET /api/v1/admin/organizations/:id/members - Get organization members
|
||||
await page.route(`${baseURL}/api/v1/admin/organizations/*/members*`, async (route: Route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [],
|
||||
pagination: {
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total_pages: 1,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Mock GET /api/v1/admin/sessions - Get all sessions (for stats calculation)
|
||||
await page.route(`${baseURL}/api/v1/admin/sessions*`, async (route: Route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [MOCK_SESSION],
|
||||
pagination: {
|
||||
total: 45, // Total sessions for stats
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
total_pages: 1,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user