import { test, expect } from '@playwright/test'; type SelectionPair = { aclLabel: string; securityHeadersLabel: string; }; async function dismissDomainDialog(page: import('@playwright/test').Page): Promise { const noThanksButton = page.getByRole('button', { name: /no, thanks/i }); if (await noThanksButton.isVisible({ timeout: 1200 }).catch(() => false)) { await noThanksButton.click(); } } async function openCreateModal(page: import('@playwright/test').Page): Promise { const addButton = page.getByRole('button', { name: /add.*proxy.*host|create/i }).first(); await expect(addButton).toBeEnabled(); await addButton.click(); await expect(page.getByRole('dialog')).toBeVisible(); } async function selectFirstUsableOption( page: import('@playwright/test').Page, trigger: import('@playwright/test').Locator, skipPattern: RegExp ): Promise { await trigger.click(); const listbox = page.getByRole('listbox'); await expect(listbox).toBeVisible(); const options = listbox.getByRole('option'); const optionCount = await options.count(); expect(optionCount).toBeGreaterThan(0); for (let i = 0; i < optionCount; i++) { const option = options.nth(i); const rawLabel = (await option.textContent())?.trim() || ''; const isDisabled = (await option.getAttribute('aria-disabled')) === 'true'; if (isDisabled || !rawLabel || skipPattern.test(rawLabel)) { continue; } await option.click(); return rawLabel; } throw new Error('No selectable non-default option found in dropdown'); } async function selectOptionByName( page: import('@playwright/test').Page, trigger: import('@playwright/test').Locator, optionName: RegExp ): Promise { await trigger.click(); const listbox = page.getByRole('listbox'); await expect(listbox).toBeVisible(); const option = listbox.getByRole('option', { name: optionName }).first(); await expect(option).toBeVisible(); const label = ((await option.textContent()) || '').trim(); await option.click(); return label; } async function saveProxyHost(page: import('@playwright/test').Page): Promise { await dismissDomainDialog(page); const saveButton = page .getByTestId('proxy-host-save') .or(page.getByRole('button', { name: /^save$/i })) .first(); await expect(saveButton).toBeEnabled(); await saveButton.click(); const confirmSave = page.getByRole('button', { name: /yes.*save/i }).first(); if (await confirmSave.isVisible({ timeout: 1200 }).catch(() => false)) { await confirmSave.click(); } await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 }); } async function openEditModalForDomain(page: import('@playwright/test').Page, domain: string): Promise { const row = page.locator('tbody tr').filter({ hasText: domain }).first(); await expect(row).toBeVisible({ timeout: 10000 }); const editButton = row.getByRole('button', { name: /edit proxy host|edit/i }).first(); await expect(editButton).toBeVisible(); await editButton.click(); await expect(page.getByRole('dialog')).toBeVisible(); } async function selectNonDefaultPair( page: import('@playwright/test').Page, dialog: import('@playwright/test').Locator ): Promise { const aclTrigger = dialog.getByRole('combobox', { name: /access control list/i }); const securityHeadersTrigger = dialog.getByRole('combobox', { name: /security headers/i }); const aclLabel = await selectFirstUsableOption(page, aclTrigger, /no access control|public/i); await expect(aclTrigger).toContainText(aclLabel); const securityHeadersLabel = await selectFirstUsableOption(page, securityHeadersTrigger, /none \(no security headers\)/i); await expect(securityHeadersTrigger).toContainText(securityHeadersLabel); return { aclLabel, securityHeadersLabel }; } test.describe('ProxyHostForm ACL and Security Headers Dropdown Regression', () => { test('should keep ACL and Security Headers behavior equivalent across create/edit flows', async ({ page }) => { const suffix = Date.now(); const proxyName = `Dropdown Regression ${suffix}`; const proxyDomain = `dropdown-${suffix}.test.local`; await test.step('Navigate to Proxy Hosts', async () => { await page.goto('/proxy-hosts'); await page.waitForLoadState('networkidle'); await expect(page.getByRole('heading', { name: /proxy hosts/i }).first()).toBeVisible(); }); await test.step('Create flow: select ACL + Security Headers and verify immediate form state', async () => { await openCreateModal(page); const dialog = page.getByRole('dialog'); await dialog.locator('#proxy-name').fill(proxyName); await dialog.locator('#domain-names').click(); await page.keyboard.type(proxyDomain); await page.keyboard.press('Tab'); await dismissDomainDialog(page); await dialog.locator('#forward-host').fill('127.0.0.1'); await dialog.locator('#forward-port').fill('8080'); const initialSelection = await selectNonDefaultPair(page, dialog); await saveProxyHost(page); await openEditModalForDomain(page, proxyDomain); const reopenDialog = page.getByRole('dialog'); await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(initialSelection.aclLabel); await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(initialSelection.securityHeadersLabel); await reopenDialog.getByRole('button', { name: /cancel/i }).click(); await expect(reopenDialog).not.toBeVisible({ timeout: 5000 }); }); await test.step('Edit flow: change ACL + Security Headers and verify persisted updates', async () => { await openEditModalForDomain(page, proxyDomain); const dialog = page.getByRole('dialog'); const updatedSelection = await selectNonDefaultPair(page, dialog); await saveProxyHost(page); await openEditModalForDomain(page, proxyDomain); const reopenDialog = page.getByRole('dialog'); await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(updatedSelection.aclLabel); await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(updatedSelection.securityHeadersLabel); await reopenDialog.getByRole('button', { name: /cancel/i }).click(); await expect(reopenDialog).not.toBeVisible({ timeout: 5000 }); }); await test.step('Edit flow: clear both to none/null and verify persisted clearing', async () => { await openEditModalForDomain(page, proxyDomain); const dialog = page.getByRole('dialog'); const aclTrigger = dialog.getByRole('combobox', { name: /access control list/i }); const securityHeadersTrigger = dialog.getByRole('combobox', { name: /security headers/i }); const aclNoneLabel = await selectOptionByName(page, aclTrigger, /no access control \(public\)/i); await expect(aclTrigger).toContainText(aclNoneLabel); const securityNoneLabel = await selectOptionByName(page, securityHeadersTrigger, /none \(no security headers\)/i); await expect(securityHeadersTrigger).toContainText(securityNoneLabel); await saveProxyHost(page); await openEditModalForDomain(page, proxyDomain); const reopenDialog = page.getByRole('dialog'); await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(/no access control \(public\)/i); await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(/none \(no security headers\)/i); await reopenDialog.getByRole('button', { name: /cancel/i }).click(); await expect(reopenDialog).not.toBeVisible({ timeout: 5000 }); }); }); });