// XCA-style subject alternative name editor on the certificate form. const { test, expect } = require("@playwright/test"); function unique(prefix) { return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}`; } async function field(page, label) { return page.locator("label").filter({ hasText: label }).first().locator("..").locator("input, select, textarea").first(); } async function login(page, username) { await page.goto("/login"); await page.getByRole("link", { name: "Sign in with default" }).click(); await page.getByPlaceholder("Enter any user/subject").fill(username); await page.getByRole("button", { name: "Sign-in" }).click(); await expect(page.getByRole("heading", { name: "Vaults" })).toBeVisible(); } test("XCA-style subject alternative name editor", async ({ page }) => { const user = unique("san"); const vaultName = unique("SAN"); const certName = unique("san.example"); await login(page, user); await page.getByRole("button", { name: "New vault" }).click(); await page.getByPlaceholder("Production PKI").fill(vaultName); await page.getByRole("button", { name: "Create vault" }).click(); await page.getByRole("link", { name: vaultName }).click(); const vaultUrl = page.url(); const settingsResponse = page.waitForResponse( (response) => response.url().includes("/api/vaults/") && response.url().endsWith("/settings") && response.ok() ); await page.goto(`${vaultUrl}/certificates/new`); await settingsResponse; await (await field(page, "Internal name")).fill(certName); await (await field(page, "Common name (CN)")).fill(certName); await page.locator("#new-key").check(); // Cancelling discards the draft rows and leaves the original input untouched. await page.locator("#edit-san").click(); let dialog = page.getByRole("dialog", { name: "Subject alternative names" }); await expect(dialog).toBeVisible(); await dialog.getByRole("button", { name: "Add", exact: true }).click(); await dialog.locator(".san-entry .san-value").fill("discarded.example"); await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); await expect(dialog).toHaveCount(0); await expect(page.locator("#san-text")).toHaveValue(""); await page.locator("#edit-san").click(); dialog = page.getByRole("dialog", { name: "Subject alternative names" }); await expect(dialog.locator(".san-entry")).toHaveCount(0); await dialog.getByLabel("Critical", { exact: true }).check(); await dialog.getByLabel("Copy Common Name", { exact: true }).check(); await dialog.getByRole("button", { name: "Add", exact: true }).click(); await dialog.locator(".san-entry").nth(0).locator("select").selectOption("DNS"); await dialog.locator(".san-entry").nth(0).locator(".san-value").fill(`www.${certName}`); await dialog.getByRole("button", { name: "Add", exact: true }).click(); await dialog.locator(".san-entry").nth(1).locator("select").selectOption("IP"); await dialog.locator(".san-entry").nth(1).locator(".san-value").fill("192.0.2.10"); // Row deletion works without disturbing the remaining entries. await dialog.getByRole("button", { name: "Add", exact: true }).click(); await dialog.locator(".san-entry").nth(2).locator(".san-value").fill("discarded.example"); await dialog.locator(".san-entry").nth(2).getByRole("button", { name: "Delete", exact: true }).click(); await expect(dialog.locator(".san-entry")).toHaveCount(2); await dialog.getByRole("button", { name: "Apply", exact: true }).click(); await expect(dialog).toHaveCount(0); await expect(page.locator("#san-text")).toHaveValue( `DNS:copycn, DNS:www.${certName}, IP:192.0.2.10` ); // Reopening reconstructs the draft from the applied value and criticality. await page.locator("#edit-san").click(); dialog = page.getByRole("dialog", { name: "Subject alternative names" }); await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked(); await expect(dialog.getByLabel("Copy Common Name", { exact: true })).toBeChecked(); await expect(dialog.locator(".san-entry")).toHaveCount(2); await expect(dialog.locator(".san-entry").nth(0).locator("select")).toHaveValue("DNS"); await expect(dialog.locator(".san-entry").nth(0).locator(".san-value")).toHaveValue(`www.${certName}`); await expect(dialog.locator(".san-entry").nth(1).locator("select")).toHaveValue("IP"); await expect(dialog.locator(".san-entry").nth(1).locator(".san-value")).toHaveValue("192.0.2.10"); await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); await page.getByRole("button", { name: "Validate extensions" }).click(); await expect(page.getByText("Extension preview")).toBeVisible(); await expect(page.getByText("192.0.2.10", { exact: false })).toBeVisible(); await page.getByRole("button", { name: "Create certificate" }).click(); await expect(page.getByRole("heading", { name: certName })).toBeVisible(); const certApiUrl = page.url().replace(/\/vaults\//, "/api/vaults/"); const detail = await page.evaluate(async (url) => { const response = await fetch(url); return response.json(); }, certApiUrl); expect(detail.extension_spec.subject_alt_name).toEqual({ critical: true, entries: [ { type: "dns", value: certName }, { type: "dns", value: `www.${certName}` }, { type: "ip", value: "192.0.2.10" }, ], }); await page.locator(".nav-tabs").getByRole("tab", { name: "Extensions", exact: true }).click(); await expect(page.getByText("192.0.2.10", { exact: false })).toBeVisible(); });