20260617 增加RPKI Explorer前端
This commit is contained in:
50
ui/rpki-explorer/tests/e2e/object-detail-api.spec.ts
Normal file
50
ui/rpki-explorer/tests/e2e/object-detail-api.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.setTimeout(120_000);
|
||||
|
||||
test("renders live object detail and lazy tab requests", async ({ page }) => {
|
||||
const apiRequests: string[] = [];
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
page.on("request", (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.pathname.startsWith("/api/v1")) {
|
||||
apiRequests.push(`${url.pathname}${url.search}`);
|
||||
}
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: /Objects/i }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "MFT object" })).toBeVisible({ timeout: 70_000 });
|
||||
await expect(page.getByText("5A179648B3EF2369DCE7BDB58140FF7DC7060ABF.mft").first()).toBeVisible();
|
||||
await expect(page.getByText("Final status: valid")).toBeVisible();
|
||||
const objectDetail = page.getByRole("complementary", { name: "Live object detail" });
|
||||
await expect(objectDetail.getByText("Authoritative", { exact: true })).toBeVisible();
|
||||
expect(apiRequests.some((request) => request.includes("/objects/ff44545d40ad3732405b46ec/parsed"))).toBe(false);
|
||||
|
||||
await page.getByRole("tab", { name: "Parsed" }).click();
|
||||
await expect(page.getByText("Projection unavailable")).toBeVisible({ timeout: 30_000 });
|
||||
expect(apiRequests.some((request) => request.includes("/objects/ff44545d40ad3732405b46ec/parsed"))).toBe(true);
|
||||
|
||||
await page.getByRole("tab", { name: "Chain" }).click();
|
||||
await expect(page.getByText("No chain edges recorded for this object.")).toBeVisible({ timeout: 30_000 });
|
||||
expect(apiRequests.some((request) => request.includes("/objects/ff44545d40ad3732405b46ec/chain"))).toBe(true);
|
||||
|
||||
await page.getByRole("tab", { name: "Validation" }).click();
|
||||
await page.getByRole("button", { name: "Explain validation" }).click();
|
||||
await expect(page.getByText("audit_projection")).toBeVisible({ timeout: 30_000 });
|
||||
await expect(objectDetail.getByText("false").last()).toBeVisible();
|
||||
expect(apiRequests.some((request) => request.includes("/validation/explain"))).toBe(true);
|
||||
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m5_playwright/rpki-explorer-object-detail-live.png",
|
||||
fullPage: true
|
||||
});
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
39
ui/rpki-explorer/tests/e2e/overview-api.spec.ts
Normal file
39
ui/rpki-explorer/tests/e2e/overview-api.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("renders live overview data from query service without global object list", async ({ page }) => {
|
||||
const apiRequests: string[] = [];
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
page.on("request", (request) => {
|
||||
const path = new URL(request.url()).pathname;
|
||||
if (path.startsWith("/api/v1")) {
|
||||
apiRequests.push(path);
|
||||
}
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByText("run 7144")).toBeVisible();
|
||||
await expect(page.getByText("963,779")).toBeVisible();
|
||||
await expect(page.getByText("534,760")).toBeVisible();
|
||||
await expect(page.getByText("Top repositories by workload")).toBeVisible();
|
||||
await expect(page.locator(".issue-card").first().getByText("Reject")).toBeVisible();
|
||||
|
||||
expect(apiRequests).toContain("/api/v1/latest_run");
|
||||
expect(apiRequests).toContain("/api/v1/runs/run_7144/stats/object-types");
|
||||
expect(apiRequests).toContain("/api/v1/runs/run_7144/stats/validation");
|
||||
expect(apiRequests).toContain("/api/v1/runs/run_7144/stats/reasons");
|
||||
expect(apiRequests).toContain("/api/v1/runs/run_7144/repos");
|
||||
expect(apiRequests).not.toContain("/api/v1/runs/run_7144/objects");
|
||||
expect(consoleErrors).toEqual([]);
|
||||
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m3_playwright/rpki-explorer-overview-live.png",
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
51
ui/rpki-explorer/tests/e2e/repositories-api.spec.ts
Normal file
51
ui/rpki-explorer/tests/e2e/repositories-api.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.setTimeout(120_000);
|
||||
|
||||
test("loads repository, publication point, and object data on demand", async ({ page }) => {
|
||||
const apiRequests: string[] = [];
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
page.on("request", (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.pathname.startsWith("/api/v1")) {
|
||||
apiRequests.push(`${url.pathname}${url.search}`);
|
||||
}
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: /Repositories/i }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Repository / publication point / object browser" })).toBeVisible();
|
||||
await expect(page.getByText("Select a repository to load its publication points.")).toBeVisible();
|
||||
expect(apiRequests.some((request) => request.includes("/publication-points"))).toBe(false);
|
||||
expect(apiRequests.some((request) => request.includes("/objects"))).toBe(false);
|
||||
|
||||
await page.getByRole("button", { name: /sakuya\.nat\.moe/i }).click();
|
||||
await expect(page.getByRole("button", { name: /5A179648B3EF2369DCE7BDB58140FF7DC7060ABF\.mft/i })).toBeVisible({ timeout: 30_000 });
|
||||
expect(apiRequests.some((request) => request.includes("/repos/0490c1fe6e4d4ae5cc354948/publication-points"))).toBe(true);
|
||||
expect(apiRequests.some((request) => request.includes("/objects"))).toBe(false);
|
||||
|
||||
await page.getByRole("button", { name: /5A179648B3EF2369DCE7BDB58140FF7DC7060ABF\.mft/i }).click();
|
||||
await expect.poll(
|
||||
() => apiRequests.some((request) => request.includes("/publication-points/node_10342/objects")),
|
||||
{ timeout: 70_000 }
|
||||
).toBe(true);
|
||||
const objectsPanel = page.getByRole("region", { name: "Objects for publication point" });
|
||||
await expect(
|
||||
objectsPanel.getByText(/rsync:\/\/sakuya\.nat\.moe\/repo\/NATOCA\/1\/5A179648B3EF2369DCE7BDB58140FF7DC7060ABF\.mft/)
|
||||
).toBeVisible({ timeout: 70_000 });
|
||||
await expect(objectsPanel.getByText("manifest").first()).toBeVisible();
|
||||
await expect(page.getByText("limit=50")).toBeVisible();
|
||||
expect(consoleErrors).toEqual([]);
|
||||
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m4_playwright/rpki-explorer-repository-browser.png",
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
56
ui/rpki-explorer/tests/e2e/shell.spec.ts
Normal file
56
ui/rpki-explorer/tests/e2e/shell.spec.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("opens the RPKI Explorer overview prototype", async ({ page }) => {
|
||||
const consoleErrors: string[] = [];
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
const text = message.text();
|
||||
if (!text.includes("Failed to load resource")) {
|
||||
consoleErrors.push(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "RPKI Explorer" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Global RPKI validation health" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /Overview/i })).toBeVisible();
|
||||
await expect(page.getByText("Top repositories by workload")).toBeVisible();
|
||||
await expect(page.getByText("Recent validation issues")).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: /RRDP|RSYNC/i }).first()).toBeVisible();
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m2_playwright/rpki-explorer-overview.png",
|
||||
fullPage: true
|
||||
});
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test("opens the RPKI Explorer object detail prototype", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
const consoleErrors: string[] = [];
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
const text = message.text();
|
||||
if (!text.includes("Failed to load resource")) {
|
||||
consoleErrors.push(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: /Objects/i }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "MFT object" })).toBeVisible({ timeout: 70_000 });
|
||||
await expect(page.getByText("Object detail · live query service")).toBeVisible();
|
||||
await expect(page.getByText("File and chain checks")).toBeVisible();
|
||||
await expect(page.getByRole("tab", { name: "Validation" })).toBeVisible();
|
||||
await page.getByRole("tab", { name: "Validation" }).click();
|
||||
await expect(page.getByText("Final status: valid")).toBeVisible();
|
||||
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m2_playwright/rpki-explorer-object-detail.png",
|
||||
fullPage: true
|
||||
});
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
40
ui/rpki-explorer/tests/e2e/workflows-api.spec.ts
Normal file
40
ui/rpki-explorer/tests/e2e/workflows-api.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.setTimeout(120_000);
|
||||
|
||||
test("supports URI search and reports raw/export workflow status", async ({ page }) => {
|
||||
const apiRequests: string[] = [];
|
||||
page.on("request", (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.pathname.startsWith("/api/v1")) {
|
||||
apiRequests.push(`${request.method()} ${url.pathname}${url.search}`);
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: /Objects/i }).click();
|
||||
await expect(page.getByRole("heading", { name: "MFT object" })).toBeVisible({ timeout: 70_000 });
|
||||
|
||||
await page.getByRole("button", { name: "Use selected URI" }).click();
|
||||
await page.getByPlaceholder("Exact URI lookup...").press("Enter");
|
||||
await expect.poll(
|
||||
() => apiRequests.some((request) => request.includes("/objects/by-uri")),
|
||||
{ timeout: 30_000 }
|
||||
).toBe(true);
|
||||
|
||||
await page.getByRole("button", { name: "Download raw" }).click();
|
||||
await expect(page.getByText("Raw download failed: repo-bytes db is not configured for raw download")).toBeVisible({ timeout: 30_000 });
|
||||
expect(apiRequests.some((request) => request.includes("/objects/ff44545d40ad3732405b46ec/raw"))).toBe(true);
|
||||
|
||||
await page.getByRole("button", { name: "Export object" }).click();
|
||||
await expect(page.getByText("Object export failed: repo-bytes db is required for export jobs")).toBeVisible({ timeout: 30_000 });
|
||||
expect(apiRequests.some((request) => request.includes("POST /api/v1/runs/run_7144/exports"))).toBe(true);
|
||||
|
||||
await page.getByRole("button", { name: "Export selected PP" }).click();
|
||||
await expect(page.getByText("PP export failed: repo-bytes db is required for export jobs")).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.screenshot({
|
||||
path: "../../../../specs/develop/20260617/m6_playwright/rpki-explorer-workflows.png",
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user