mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-03 17:54:05 +00:00
test(ui): stabilize attack-paths refit integration test (#12896)
This commit is contained in:
@@ -8,6 +8,8 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { BrowserHarness } from "./browser-harness";
|
||||
|
||||
const QUIET_MS = 50;
|
||||
|
||||
/** Exposes the protected waiting helpers; no fixture or DOM is involved. */
|
||||
class WaitingHarness extends BrowserHarness<null> {
|
||||
constructor() {
|
||||
@@ -21,6 +23,10 @@ class WaitingHarness extends BrowserHarness<null> {
|
||||
probeOrNull<T>(fn: () => T | null | undefined | false): Promise<T | null> {
|
||||
return this.waitForOrNull(fn, 200, "probe");
|
||||
}
|
||||
|
||||
probeStable<T>(read: () => T): Promise<T> {
|
||||
return this.waitForStable(read, QUIET_MS, 1000, "probe");
|
||||
}
|
||||
}
|
||||
|
||||
describe("BrowserHarness waiting helpers", () => {
|
||||
@@ -58,4 +64,20 @@ describe("BrowserHarness waiting helpers", () => {
|
||||
}),
|
||||
).resolves.toBe("ready");
|
||||
});
|
||||
|
||||
it("resolves with a value only once it has held for the quiet window", async () => {
|
||||
const harness = new WaitingHarness();
|
||||
let reads = 0;
|
||||
let settledAt = 0;
|
||||
|
||||
// Changes on each of the first reads, then holds at 4.
|
||||
const settled = await harness.probeStable(() => {
|
||||
reads += 1;
|
||||
if (reads === 4) settledAt = performance.now();
|
||||
return Math.min(reads, 4);
|
||||
});
|
||||
|
||||
expect(settled).toBe(4);
|
||||
expect(performance.now() - settledAt).toBeGreaterThanOrEqual(QUIET_MS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -211,6 +211,31 @@ export abstract class BrowserHarness<TFixture> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Wait until `read` returns the same value for `quietMs`, and return it. */
|
||||
protected async waitForStable<T>(
|
||||
read: () => T,
|
||||
quietMs: number,
|
||||
timeoutMs = 5000,
|
||||
label?: string,
|
||||
): Promise<T> {
|
||||
let value = read();
|
||||
let since = performance.now();
|
||||
const settled = await this.waitFor(
|
||||
() => {
|
||||
const next = read();
|
||||
if (!Object.is(next, value)) {
|
||||
value = next;
|
||||
since = performance.now();
|
||||
return null;
|
||||
}
|
||||
return performance.now() - since >= quietMs ? { value } : null;
|
||||
},
|
||||
timeoutMs,
|
||||
label ?? `a value stable for ${quietMs}ms`,
|
||||
);
|
||||
return settled.value;
|
||||
}
|
||||
|
||||
protected async waitForText(
|
||||
pattern: RegExp,
|
||||
timeoutMs = 5000,
|
||||
|
||||
+26
-5
@@ -18,6 +18,8 @@ export class AttackPathPageHarness extends BrowserHarness<PageFixture> {
|
||||
private static readonly VIEWPORT_SEL = ".react-flow__viewport";
|
||||
private static readonly MINIMAP_SEL = ".react-flow__minimap";
|
||||
private static readonly BACKGROUND_SEL = ".react-flow__background";
|
||||
// Matches the graph's auto-fit duration; a pause this long means no fit is mid-flight.
|
||||
private static readonly FIT_ANIMATION_MS = 300;
|
||||
|
||||
private static isFindingElement(el: Element): boolean {
|
||||
return (
|
||||
@@ -255,17 +257,31 @@ export class AttackPathPageHarness extends BrowserHarness<PageFixture> {
|
||||
/** Wait until the React Flow viewport transform changes from `previous`. */
|
||||
async waitForViewportChange(
|
||||
previous: string,
|
||||
timeoutMs = 2000,
|
||||
timeoutMs?: number,
|
||||
): Promise<void> {
|
||||
await this.waitFor(() => this.viewportTransform !== previous, timeoutMs);
|
||||
await this.waitFor(
|
||||
() => this.viewportTransform !== previous,
|
||||
timeoutMs,
|
||||
"the viewport transform to change",
|
||||
);
|
||||
}
|
||||
|
||||
/** Wait until the viewport stops moving and return its settled transform. */
|
||||
async waitForViewportSettled(): Promise<string> {
|
||||
return this.waitForStable(
|
||||
() => this.viewportTransform,
|
||||
AttackPathPageHarness.FIT_ANIMATION_MS,
|
||||
undefined,
|
||||
"the viewport to settle",
|
||||
);
|
||||
}
|
||||
|
||||
/** Wait until every requested node is fully contained in the graph canvas. */
|
||||
async waitForNodesInViewport(
|
||||
nodeIds: string[],
|
||||
timeoutMs = 2000,
|
||||
timeoutMs?: number,
|
||||
): Promise<void> {
|
||||
await this.waitFor(() => {
|
||||
const allInViewport = () => {
|
||||
const canvas = this.q(AttackPathPageHarness.FLOW_SEL);
|
||||
if (!canvas) return false;
|
||||
|
||||
@@ -282,7 +298,12 @@ export class AttackPathPageHarness extends BrowserHarness<PageFixture> {
|
||||
nodeRect.bottom <= canvasRect.bottom
|
||||
);
|
||||
});
|
||||
}, timeoutMs);
|
||||
};
|
||||
await this.waitFor(
|
||||
allInViewport,
|
||||
timeoutMs,
|
||||
`nodes ${nodeIds.join(", ")} to be in the viewport`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Wait until exactly `count` edges are highlighted. */
|
||||
|
||||
+8
-3
@@ -407,19 +407,24 @@ describe("exploring the graph", () => {
|
||||
const graph = await mountWith();
|
||||
await graph.executeQuery();
|
||||
await graph.waitForGraphStable(3);
|
||||
|
||||
const initialViewport = graph.viewportTransform;
|
||||
// Settle before each capture so the next change can only come from the
|
||||
// action under test, not the tail of the previous fit animation.
|
||||
const initialViewport = await graph.waitForViewportSettled();
|
||||
|
||||
await graph.clickFirstResourceNode();
|
||||
|
||||
expect(graph.findingNodes.length).toBeGreaterThan(0);
|
||||
await graph.waitForViewportChange(initialViewport);
|
||||
|
||||
const contextualViewport = graph.viewportTransform;
|
||||
const contextualViewport = await graph.waitForViewportSettled();
|
||||
const visibleNodeIds = graph.renderedNodeIds;
|
||||
|
||||
await graph.fit();
|
||||
|
||||
await graph.waitForViewportChange(contextualViewport);
|
||||
// The fit must end with the whole visible graph on screen, not just move
|
||||
await graph.waitForViewportSettled();
|
||||
await graph.waitForNodesInViewport(visibleNodeIds);
|
||||
});
|
||||
test("clicking an expanded resource re-fits the remaining visible graph", async ({
|
||||
mountWith,
|
||||
|
||||
Reference in New Issue
Block a user