opnsense-nordvpn-plugin/tests/ui/specs/enrollment.spec.js

115 lines
5.2 KiB
JavaScript

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 testRows = grid.getByRole('row').filter({ hasText: 'UI_TEST_NETWORK' });
while (await testRows.count()) {
const before = await testRows.count();
await testRows.first().getByRole('button', { name: 'Delete' }).click();
const removal = page.getByRole('dialog', { name: 'Remove' });
await removal.getByRole('button', { name: 'Yes' }).click();
await expect(testRows).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 testRows.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(testRows).toHaveCount(beforeDelete - 1);
expect(errors).toEqual([]);
});