95 lines
3.7 KiB
TypeScript
95 lines
3.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { login } from './helpers/auth';
|
|
|
|
test.describe('Clients', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await login(page);
|
|
await page.goto('/clients');
|
|
await page.waitForURL(/\/clients/, { timeout: 15000 });
|
|
});
|
|
|
|
test('clients list page loads with table', async ({ page }) => {
|
|
await expect(page.locator('h1:has-text("Clients")')).toBeVisible();
|
|
await expect(page.locator('table')).toBeVisible();
|
|
await expect(page.locator('th:has-text("Name")')).toBeVisible();
|
|
await expect(page.locator('th:has-text("Account #")')).toBeVisible();
|
|
await expect(page.locator('th:has-text("Status")')).toBeVisible();
|
|
});
|
|
|
|
test('search/filter works without crash', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
await expect(searchInput).toBeVisible();
|
|
await searchInput.fill('test');
|
|
// Wait for debounce/query
|
|
await page.waitForTimeout(600);
|
|
// Page should not crash
|
|
await expect(page.locator('h1:has-text("Clients")')).toBeVisible();
|
|
// Clear search
|
|
await searchInput.fill('');
|
|
await page.waitForTimeout(400);
|
|
await expect(page.locator('h1:has-text("Clients")')).toBeVisible();
|
|
});
|
|
|
|
test('Add client button is clickable and opens modal', async ({ page }) => {
|
|
// Use testid if available, fall back to text
|
|
const btn = page.locator('[data-testid="add-client-btn"], button:has-text("Add Client")').first();
|
|
await expect(btn).toBeVisible();
|
|
await btn.click();
|
|
// Modal should open
|
|
await expect(page.locator('text=Add New Client')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('clicking a client row navigates to detail page', async ({ page }) => {
|
|
// Wait for data to load (skeleton rows disappear)
|
|
await page.waitForTimeout(2000);
|
|
const rows = page.locator('tr.cursor-pointer');
|
|
const count = await rows.count();
|
|
if (count === 0) {
|
|
// No clients — verify empty state renders gracefully
|
|
await expect(page.locator('text=No clients found')).toBeVisible();
|
|
return;
|
|
}
|
|
await rows.first().click();
|
|
await expect(page).toHaveURL(/\/clients\/[a-zA-Z0-9-]+/, { timeout: 10000 });
|
|
});
|
|
|
|
test('client detail page tabs render', async ({ page }) => {
|
|
// Wait for data
|
|
await page.waitForTimeout(2000);
|
|
const rows = page.locator('tr.cursor-pointer');
|
|
const count = await rows.count();
|
|
if (count === 0) {
|
|
test.skip(true, 'No clients to test detail page');
|
|
return;
|
|
}
|
|
await rows.first().click();
|
|
await expect(page).toHaveURL(/\/clients\/[a-zA-Z0-9-]+/, { timeout: 10000 });
|
|
|
|
// All 5 tabs must be visible
|
|
for (const tabLabel of ['Profile', 'Subscriptions', 'Invoices', 'Payments', 'Tickets']) {
|
|
await expect(page.locator(`button:has-text("${tabLabel}")`)).toBeVisible();
|
|
}
|
|
|
|
// Click each tab and verify no crash (no "map is not a function" errors)
|
|
await page.click('button:has-text("Subscriptions")');
|
|
await page.waitForTimeout(800);
|
|
await expect(page.locator('button:has-text("Subscriptions")')).toBeVisible();
|
|
|
|
await page.click('button:has-text("Invoices")');
|
|
await page.waitForTimeout(800);
|
|
await expect(page.locator('button:has-text("Invoices")')).toBeVisible();
|
|
|
|
await page.click('button:has-text("Payments")');
|
|
await page.waitForTimeout(800);
|
|
await expect(page.locator('button:has-text("Payments")')).toBeVisible();
|
|
|
|
await page.click('button:has-text("Tickets")');
|
|
await page.waitForTimeout(800);
|
|
await expect(page.locator('button:has-text("Tickets")')).toBeVisible();
|
|
|
|
// Back to Profile
|
|
await page.click('button:has-text("Profile")');
|
|
await expect(page.locator('text=Client Profile')).toBeVisible();
|
|
});
|
|
});
|