- Fix dialog scrollability (flex layout + max-h-[90dvh]) and increase L4 dialog to lg width - Add styled enable card to L4 dialog matching proxy host pattern - Unify section colors across proxy host and L4 dialogs (cyan=LB, emerald=DNS, violet=upstream DNS, rose=geo, amber=mTLS) - Improve light mode contrast: muted-foreground oklch 0.552→0.502, remove opacity modifiers on secondary text - Improve dark mode: boost muted-foreground to 0.85, increase border opacity 10%→16%, input 15%→20% - Add bg-card to DataTable wrapper and bg-muted/40 to table headers for surface hierarchy - Add semantic badge variants (success, warning, info, muted) and StatusChip dark mode fix - Add server-side sortable columns to Proxy Hosts and L4 Proxy Hosts (name, upstream, status, protocol, listen) - Add sortKey to DataTable Column type with clickable sort headers (ArrowUp/Down indicators, URL param driven) - Fix E2E test selectors for shadcn UI (label associations, combobox roles, dropdown menus, mobile drawer) - Add htmlFor/id to proxy host form fields and aria-labels to select triggers for accessibility - Add sorting E2E tests for both proxy host pages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Proxy Hosts', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/proxy-hosts');
|
|
});
|
|
|
|
test('page loads with Create Host button visible', async ({ page }) => {
|
|
await expect(page.getByRole('button', { name: /create host/i })).toBeVisible();
|
|
});
|
|
|
|
test('clicking Create Host opens a dialog with form fields', async ({ page }) => {
|
|
await page.getByRole('button', { name: /create host/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
await expect(page.getByLabel(/domains/i)).toBeVisible();
|
|
});
|
|
|
|
test('create a proxy host — appears in the table', async ({ page }) => {
|
|
await page.getByRole('button', { name: /create host/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
await page.getByLabel('Name').fill('E2E Test Host');
|
|
await page.getByLabel(/domains/i).fill('e2etest.local');
|
|
// Upstream field uses placeholder text, not a label
|
|
await page.getByPlaceholder('10.0.0.5:8080').fill('localhost:9999');
|
|
|
|
await page.getByRole('button', { name: /^create$/i }).click();
|
|
|
|
// Dialog should close and host appear in table
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
|
await expect(page.getByRole('table').getByText('E2E Test Host')).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('clicking Name / Domain header sorts the table', async ({ page }) => {
|
|
const sortBtn = page.getByRole('button', { name: 'Name / Domain' });
|
|
await expect(sortBtn).toBeVisible();
|
|
|
|
// Click to sort ascending
|
|
await sortBtn.click();
|
|
await expect(page).toHaveURL(/sortBy=name/);
|
|
await expect(page).toHaveURL(/sortDir=asc/);
|
|
|
|
// Click again to toggle to descending
|
|
await sortBtn.click();
|
|
await expect(page).toHaveURL(/sortDir=desc/);
|
|
});
|
|
|
|
test('clicking Status header sorts by enabled state', async ({ page }) => {
|
|
const sortBtn = page.getByRole('button', { name: 'Status' });
|
|
await expect(sortBtn).toBeVisible();
|
|
|
|
await sortBtn.click();
|
|
await expect(page).toHaveURL(/sortBy=enabled/);
|
|
});
|
|
|
|
test('delete proxy host removes it from table', async ({ page }) => {
|
|
// Create one to delete
|
|
await page.getByRole('button', { name: /create host/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
await page.getByLabel('Name').fill('Host To Delete');
|
|
await page.getByLabel(/domains/i).fill('delete-me.local');
|
|
await page.getByPlaceholder('10.0.0.5:8080').fill('localhost:7777');
|
|
await page.getByRole('button', { name: /^create$/i }).click();
|
|
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
|
await expect(page.getByRole('table').getByText('Host To Delete')).toBeVisible({ timeout: 10000 });
|
|
|
|
// Open the dropdown menu for that row and click Delete
|
|
const row = page.locator('tr', { hasText: 'Host To Delete' });
|
|
await row.getByRole('button').first().click();
|
|
await page.getByRole('menuitem', { name: /delete/i }).click();
|
|
|
|
// Confirm dialog
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
await page.getByRole('button', { name: /^delete$/i }).click();
|
|
|
|
// Wait for dialog to close, then verify the row is gone from the table
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('tbody').getByText('Host To Delete')).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|