feat: add topology graph v0.2.2 contract
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { createServer, type Server } from "node:http";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -21,6 +22,20 @@ async function withTempOutput<T>(name: string, callback: (out: string) => Promis
|
||||
}
|
||||
}
|
||||
|
||||
async function withServer<T>(handler: Parameters<typeof createServer>[0], callback: (url: string) => Promise<T>): Promise<T> {
|
||||
const server = createServer(handler);
|
||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const address = server.address();
|
||||
assert.ok(address && typeof address === "object");
|
||||
try {
|
||||
return await callback(`http://127.0.0.1:${address.port}/`);
|
||||
} finally {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
(server as Server).close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hasEdgeForAccessibleName(
|
||||
graph: {
|
||||
edges: Array<{ elementId: string }>;
|
||||
@@ -73,7 +88,7 @@ test("duplicated row actions are verified with semantic locator evidence", async
|
||||
test("graph metadata records generation context and policy versions", async () => {
|
||||
const graph = await withTempOutput("metadata", (out) => scanUrl({ url: fixturePath, out }));
|
||||
assert.equal(graph.schemaVersion, "0.2");
|
||||
assert.equal(graph.metadata.engineVersion, "0.2.0");
|
||||
assert.equal(graph.metadata.engineVersion, "0.2.2");
|
||||
assert.equal(graph.metadata.runtimeContext.browserName, "chromium");
|
||||
assert.equal(graph.metadata.runtimeContext.viewport.width, 1440);
|
||||
assert.equal(graph.metadata.runtimeContext.viewport.height, 1000);
|
||||
@@ -297,6 +312,7 @@ test("explore artifact preserves referential integrity and snapshot-diff effect
|
||||
}
|
||||
|
||||
for (const failure of graph.failedObservations) {
|
||||
if (failure.stateId === null) continue;
|
||||
assert.equal(stateIds.has(failure.stateId), true);
|
||||
if (failure.elementId === null) continue;
|
||||
const element = graph.elements.find((item) => item.elementId === failure.elementId);
|
||||
@@ -385,6 +401,35 @@ test("explore records locator failures instead of edges or fake effects for unsa
|
||||
);
|
||||
});
|
||||
|
||||
test("explore records replay navigation timeout as a failed observation", async () => {
|
||||
let requestCount = 0;
|
||||
await withServer(
|
||||
(_request, response) => {
|
||||
requestCount += 1;
|
||||
if (requestCount === 1) {
|
||||
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
||||
response.end("<!doctype html><button>Open panel</button>");
|
||||
return;
|
||||
}
|
||||
// Leave replay navigation hanging so the engine must record a timeout.
|
||||
},
|
||||
async (url) => {
|
||||
const graph = await withTempOutput("explore-navigation-timeout", (out) =>
|
||||
exploreUrl({ url, maxActions: 1, navigationTimeoutMs: 50, out }),
|
||||
);
|
||||
const button = graph.elements.find((element) => element.accessibleName === "Open panel");
|
||||
assert.ok(button);
|
||||
assert.equal(graph.edges.length, 0);
|
||||
assert.equal(
|
||||
graph.failedObservations.some(
|
||||
(failure) => failure.elementId === button.elementId && failure.reason === "navigation_timeout",
|
||||
),
|
||||
true,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("explore records state-local skipped high-risk actions with evidence", async () => {
|
||||
const graph = await withTempOutput("explore-skips", (out) => exploreUrl({ url: fixturePath, maxActions: 12, out }));
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.reason.includes("form_submit")), true);
|
||||
|
||||
Reference in New Issue
Block a user