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

175 lines
6.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" },
],
}),
});
});
let startBootstrap;
const bootstrapStarted = new Promise((resolve) => {
startBootstrap = resolve;
});
let finishBootstrap;
await page.route("**/api/nordvpn/service/bootstrap", async (route) => {
startBootstrap();
await new Promise((resolve) => {
finishBootstrap = resolve;
});
await route.fulfill({
contentType: "application/json",
body: JSON.stringify({ status: "ok", tunnel: {} }),
});
});
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 accessToken = page.locator("#accessToken");
const bootstrap = page.getByRole("button", { name: "Bootstrap NordLynx" });
await accessToken.fill("synthetic-token");
await bootstrap.click();
await bootstrapStarted;
await expect(accessToken).toHaveValue("");
await expect(bootstrap).toBeDisabled();
finishBootstrap();
await expect(bootstrap).toBeEnabled();
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([]);
});