const { test, expect } = require('@playwright/test'); const username = process.env.OPNSENSE_UI_USERNAME; const password = process.env.OPNSENSE_UI_PASSWORD; if (!username || !password) { throw new Error('Set OPNSENSE_UI_USERNAME and OPNSENSE_UI_PASSWORD for the disposable OPNsense deployment.'); } if (process.env.OPNSENSE_UI_TEST_ALLOW_MUTATION !== '1') { throw new Error('Set OPNSENSE_UI_TEST_ALLOW_MUTATION=1 to allow enrollment changes in the disposable deployment.'); } test('renders and preserves NordVPN enrollment controls', async ({ page }) => { const errors = []; page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()); }); page.on('pageerror', (error) => errors.push(error.stack || error.message)); page.on('response', (response) => { if (response.status() >= 400) errors.push(`${response.status()} ${response.url()}`); }); await page.route('**/api/nordvpn/service/countries', async (route) => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ status: 'ok', countries: [ { code: 'XA', name: 'Synthetic A' }, { code: 'XB', name: 'Synthetic B' }, ], }), }); }); await page.goto('/'); await page.locator('#usernamefld').fill(username); await page.locator('#passwordfld').fill(password); await page.getByRole('button', { name: 'Login' }).click(); await page.goto('/ui/nordvpn'); await expect(page.getByRole('heading', { name: 'NordVPN egress enrollment' })).toBeVisible(); const countries = page.locator('[id="settings.general.countries"]'); await expect(countries.locator('option[value="XA"]')).toHaveText('Synthetic A'); await expect(countries.locator('option[value="XB"]')).toHaveText('Synthetic B'); await expect(countries.locator('xpath=..').locator('button.dropdown-toggle')).toBeVisible(); const failurePolicy = page.locator('[id="settings.general.failure_policy"]'); await expect(failurePolicy.locator('option[value="fail_closed"]')).toHaveText('Block traffic'); await expect(failurePolicy.locator('option[value="fail_open"]')).toHaveText('Use direct egress'); const grid = page.getByRole('grid'); const deleteButtons = grid.getByRole('button', { name: 'Delete', exact: true }); while (await deleteButtons.count()) { const before = await deleteButtons.count(); await deleteButtons.first().click(); const removal = page.getByRole('dialog', { name: 'Remove' }); await removal.getByRole('button', { name: 'Yes' }).click(); await expect(deleteButtons).toHaveCount(before - 1); } await page.getByRole('button', { name: 'Add', exact: true }).click(); const dialog = page.locator('.modal:visible'); await expect(dialog).toBeVisible(); await dialog.locator('[id="enrollment.interfaces"]').selectOption('lan'); const source = dialog.locator('select[for="enrollment.source"]'); const destination = dialog.locator('select[for="enrollment.destination"]'); await expect(source).toBeVisible(); await expect(destination).toBeVisible(); await expect(source.locator('option[value="UI_TEST_NETWORK"]')).toHaveText('UI_TEST_NETWORK'); const destinationPicker = destination.locator('xpath=..'); const destinationToggle = destinationPicker.locator('button.dropdown-toggle'); await destinationToggle.click(); const destinationMenu = destinationPicker.locator('.dropdown-menu.open').first(); await expect(destinationMenu).toBeVisible(); const menuBox = await destinationMenu.boundingBox(); const footerBox = await dialog.locator('.modal-footer').boundingBox(); expect(menuBox.y + menuBox.height).toBeLessThanOrEqual(footerBox.y); await destinationToggle.click(); await source.selectOption('UI_TEST_NETWORK'); await destination.selectOption('any'); await dialog.getByRole('button', { name: 'Save', exact: true }).click(); await expect(dialog).toBeHidden(); await page.reload(); await expect(page.getByRole('heading', { name: 'NordVPN egress enrollment' })).toBeVisible(); const row = page.getByRole('row').filter({ hasText: 'LAN' }).filter({ hasText: 'UI_TEST_NETWORK' }).first(); await expect(row).toBeVisible(); await row.getByRole('button', { name: 'Edit' }).click(); await expect(dialog).toBeVisible(); await expect(dialog.locator('select[for="enrollment.source"]')).toHaveValues(['UI_TEST_NETWORK']); await expect(dialog.locator('select[for="enrollment.destination"]')).toHaveValues(['any']); await dialog.getByRole('button', { name: 'Close' }).click(); await expect(dialog).toBeHidden(); await row.getByRole('button', { name: 'Clone' }).click(); await expect(dialog).toBeVisible(); await expect(dialog.locator('select[for="enrollment.source"]')).toHaveValues(['UI_TEST_NETWORK']); await expect(dialog.locator('select[for="enrollment.destination"]')).toHaveValues(['any']); await dialog.getByRole('button', { name: 'Close' }).click(); await expect(dialog).toBeHidden(); const beforeDelete = await deleteButtons.count(); await row.getByRole('button', { name: 'Delete' }).click(); const removal = page.getByRole('dialog', { name: 'Remove' }); await expect(removal).toBeVisible(); await removal.getByRole('button', { name: 'Yes' }).click(); await expect(deleteButtons).toHaveCount(beforeDelete - 1); expect(errors).toEqual([]); });