Files
dashboard/e2e/tests/dns-zones.spec.ts
Maycon Santos 7653e3411c Merge NetBird cloud edition into the dashboard (#674)
Brings the unified dashboard into the open-source repo. Premium features
ship in the open code, gated at runtime via NETBIRD_CLOUD and
NETBIRD_LICENSED, with upgrade prompts for unlicensed self-hosted
deployments. Adds the cloud-only feature areas (billing, integrations,
MSP, traffic events, notifications) and the Playwright e2e suite.
2026-06-21 16:01:08 +02:00

198 lines
8.8 KiB
TypeScript

import { test, expect } from "../helpers/fixtures";
import { navigateTo } from "../helpers/auth";
import { applyRadioTableFilter, generateRandomName } from "../helpers/utils";
import { deleteGroupsByPrefix, deleteDnsZonesByPrefix } from "../helpers/api";
let zoneDomain = "";
let zoneGroup = "";
let zoneGroup2 = "";
test.describe.serial("DNS - Zones @dns", () => {
test("Should add a new zone with a distribution group", async ({ dashboardAsOwner: page }) => {
// Clean up leftover zones from previous runs
await deleteDnsZonesByPrefix(page, "dns-zone-");
await deleteGroupsByPrefix(page, "zone-group-");
await navigateTo(page, "/dns/zones");
const name = generateRandomName("dns-zone-");
zoneDomain = `${name}.test`;
await page.getByTestId("add-dns-zone").click();
await page.getByTestId("dns-zone-domain-input").fill(zoneDomain);
const groupName = generateRandomName("zone-group-");
zoneGroup = groupName;
await page.getByTestId("dns-zone-groups-selector").click();
await page.getByTestId("dns-zone-groups-selector-search").fill(groupName);
await page.getByTestId("dns-zone-groups-selector-search").press("Enter");
await page.getByTestId("dns-zone-groups-selector-search").press("Escape");
await page.getByTestId("dns-zone-search-domains").click();
await expect(page.getByTestId("dns-zone-enabled")).toHaveAttribute("data-state", "checked");
await page.getByTestId("submit-dns-zone").click();
await expect(page.locator("tr").filter({ hasText: zoneDomain })).toBeVisible();
});
test("Should add A, AAAA, and CNAME records", async ({ dashboardAsOwner: page }) => {
const zoneRow = page.locator("tr").filter({ hasText: zoneDomain });
// Dismiss or use the "Add Record" prompt from zone creation
const addRecordBtn = page.getByTestId("confirmation.confirm");
if (await addRecordBtn.isVisible().catch(() => false)) {
await addRecordBtn.click({ force: true });
} else {
await zoneRow.getByTestId("add-dns-record").click({ force: true });
}
await expect(page.getByTestId("dns-record-hostname-input")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("dns-record-hostname-input").fill("server1");
await page.getByTestId("dns-record-content-input").fill("10.0.0.10");
await page.getByTestId("dns-record-ttl-select").click();
await page.locator('[role="option"]').filter({ hasText: "1 Min." }).click({ force: true });
await page.getByTestId("submit-dns-record").click();
await expect(zoneRow.getByTestId("dns-zone-records-count")).toContainText("1");
// AAAA record
await zoneRow.getByTestId("add-dns-record").click({ force: true });
await page.getByTestId("dns-record-type-select").click();
await page.locator('[role="option"]').filter({ hasText: "AAAA" }).click({ force: true });
await page.getByTestId("dns-record-hostname-input").fill("server2");
await page.getByTestId("dns-record-content-input").fill("2001:db8::1");
await page.getByTestId("submit-dns-record").click();
await expect(zoneRow.getByTestId("dns-zone-records-count")).toContainText("2");
// CNAME record
await zoneRow.getByTestId("add-dns-record").click({ force: true });
await page.getByTestId("dns-record-type-select").click();
await page.locator('[role="option"]').filter({ hasText: "CNAME" }).click({ force: true });
await page.getByTestId("dns-record-hostname-input").fill("alias");
await page.getByTestId("dns-record-content-input").fill("server1.example.com");
await page.getByTestId("submit-dns-record").click();
await expect(zoneRow.getByTestId("dns-zone-records-count")).toContainText("3");
});
test("Should edit a record", async ({ dashboardAsOwner: page }) => {
await page.reload();
// Expand accordion to show records
await page.locator("tr").filter({ hasText: zoneDomain }).first().click({ force: true });
await expect(page.getByText("10.0.0.10")).toBeVisible({ timeout: 10_000 });
// Edit A record
await page.getByTestId("edit-dns-record").first().click({ force: true });
await page.getByTestId("dns-record-hostname-input").fill("web1");
await page.getByTestId("dns-record-content-input").fill("10.0.0.99");
await page.getByTestId("submit-dns-record").click();
await expect(page.getByText(`web1.${zoneDomain}`).first()).toBeVisible();
});
test("Should toggle active and search domain states", async ({ dashboardAsOwner: page }) => {
await page.reload();
const row = page.locator("tr").filter({ hasText: zoneDomain });
// Active state moved into the row action menu (Enable/Disable item).
// Zone starts enabled → item reads "Disable"; toggle off then on,
// reopening the menu each time to read the updated label.
// Two races to defend against on each toggle:
// 1. Radix leaves `pointer-events: none` on body briefly during the
// close transition — re-opening without `force: true` makes
// Playwright auto-wait for the body to accept pointer events.
// 2. The toggle's PUT resolves before SWR refetches `/dns/zones`, so
// the row's `zone.enabled` is stale and the re-opened menu shows
// the old label. Wait for the GET refetch before re-opening.
const actions = row.getByTestId("dns-zone-actions");
const toggle = page.getByTestId("dns-zone-active-toggle");
const waitForRefetch = () =>
page.waitForResponse(
(r) =>
r.url().includes("/api/dns/zones") &&
r.request().method() === "GET" &&
r.ok(),
{ timeout: 10_000 },
);
await actions.click({ force: true });
let refetch = waitForRefetch();
await toggle.click({ force: true });
await refetch;
await expect(toggle).toBeHidden();
await actions.click();
await expect(toggle).toContainText("Enable");
refetch = waitForRefetch();
await toggle.click({ force: true });
await refetch;
await expect(toggle).toBeHidden();
await actions.click();
await expect(toggle).toContainText("Disable");
await page.keyboard.press("Escape");
// Toggle search domain off
const searchToggle = row.getByTestId("dns-zone-search-domain-toggle");
await searchToggle.click({ force: true });
await expect(searchToggle).toHaveAttribute("data-state", "unchecked");
});
test("Should update distribution groups", async ({ dashboardAsOwner: page }) => {
const newGroup = generateRandomName("zone-group-");
zoneGroup2 = newGroup;
await page.locator("tr").filter({ hasText: zoneDomain }).getByTestId("multiple-groups").click({ force: true });
await expect(page.getByTestId("save-groups")).toBeVisible();
await page.getByTestId("group-selector-dropdown").click();
await page.getByTestId("group-selector-dropdown-search").fill(newGroup);
await page.getByTestId("group-selector-dropdown-search").press("Enter");
await page.getByTestId("group-selector-dropdown-search").press("Escape");
await page.getByTestId("save-groups").click();
await expect(page.getByTestId("save-groups")).not.toBeVisible();
});
test("Should edit the zone and toggle settings back", async ({ dashboardAsOwner: page }) => {
// Page is on /dns/zones from previous test
await page.locator("tr").filter({ hasText: zoneDomain }).getByTestId("dns-zone-actions").click({ force: true });
await page.getByTestId("edit-dns-zone").click({ force: true });
await page.getByTestId("dns-zone-search-domains").click();
await expect(page.getByTestId("dns-zone-search-domains")).toHaveAttribute("data-state", "checked");
await page.getByTestId("submit-dns-zone").click();
});
test("Should filter and search zones", async ({ dashboardAsOwner: page }) => {
await page.reload();
const zoneRow = page.locator("tr").filter({ hasText: zoneDomain }).first();
// Filter: Active should show, Inactive should hide
await applyRadioTableFilter(page, "enabled", "Active");
await expect(zoneRow).toBeVisible();
await applyRadioTableFilter(page, "enabled", "Inactive");
await expect(zoneRow).toBeHidden();
await applyRadioTableFilter(page, "enabled", "All");
// Search by domain
const searchInput = page.getByTestId("table-search-input");
await searchInput.fill(zoneDomain);
await expect(page.locator("tr").filter({ hasText: zoneDomain })).toBeVisible();
// Search by content
await searchInput.fill("10.0.0.99");
await expect(page.locator("tr").filter({ hasText: zoneDomain })).toBeVisible();
// Search by group
await searchInput.fill(zoneGroup);
await expect(page.locator("tr").filter({ hasText: zoneDomain })).toBeVisible();
await searchInput.fill("");
});
test("Should delete the zone and groups", async ({ dashboardAsOwner: page }) => {
await deleteDnsZonesByPrefix(page, zoneDomain);
for (const group of [zoneGroup, zoneGroup2]) {
if (!group) continue;
await deleteGroupsByPrefix(page, group);
}
});
});