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