mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(vom): add record safe observations, unify definition and construction of safe DTO
This commit is contained in:
@@ -3146,11 +3146,12 @@ describe("handleSnapshot", () => {
|
|||||||
expect(result.rootFrameId).toBe("root");
|
expect(result.rootFrameId).toBe("root");
|
||||||
expect(result).not.toHaveProperty("frameDocuments");
|
expect(result).not.toHaveProperty("frameDocuments");
|
||||||
expect(result.frames).toEqual([{ frameId: "root", target: { tabId: 4 } }]);
|
expect(result.frames).toEqual([{ frameId: "root", target: { tabId: 4 } }]);
|
||||||
expect(result.nodes.find((node) => node.backendNodeId === 13)).toMatchObject({
|
expect(result.matchNodes.find((node) => node.backendNodeId === 13)).toMatchObject({
|
||||||
frameId: "root",
|
frameId: "root",
|
||||||
backendNodeId: 13,
|
backendNodeId: 13,
|
||||||
tag: "input",
|
tag: "input",
|
||||||
rect: { x: 400, y: 300, w: 200, h: 40 },
|
rect: { x: 400, y: 300, w: 200, h: 40 },
|
||||||
|
localRect: { x: 400, y: 300, w: 200, h: 40 },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3214,7 +3215,10 @@ describe("handleSnapshot", () => {
|
|||||||
expect(dumped).not.toContain(secrets.otp);
|
expect(dumped).not.toContain(secrets.otp);
|
||||||
expect(dumped).not.toContain(secrets.card);
|
expect(dumped).not.toContain(secrets.card);
|
||||||
expect(dumped).not.toContain("formValue");
|
expect(dumped).not.toContain("formValue");
|
||||||
|
expect(dumped).not.toContain("formDefaultValue");
|
||||||
expect(dumped).not.toContain("axNodes");
|
expect(dumped).not.toContain("axNodes");
|
||||||
|
expect(dumped).not.toContain("domNodes");
|
||||||
|
expect(dumped).not.toContain("attrs");
|
||||||
expect(result.text).toContain("•••");
|
expect(result.text).toContain("•••");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3231,9 +3235,10 @@ describe("handleSnapshot", () => {
|
|||||||
ownerBackendNodeId: 12,
|
ownerBackendNodeId: 12,
|
||||||
target: { tabId: 4, sessionId: "child-session" },
|
target: { tabId: 4, sessionId: "child-session" },
|
||||||
});
|
});
|
||||||
expect(result.nodes.find((node) => node.backendNodeId === 22)).toMatchObject({
|
expect(result.matchNodes.find((node) => node.backendNodeId === 22)).toMatchObject({
|
||||||
frameId: "child",
|
frameId: "child",
|
||||||
tag: "button",
|
tag: "button",
|
||||||
|
localRect: { x: 20, y: 30, w: 120, h: 40 },
|
||||||
});
|
});
|
||||||
expect(result.refs.find((ref) => ref.backendNodeId === 22)).toMatchObject({
|
expect(result.refs.find((ref) => ref.backendNodeId === 22)).toMatchObject({
|
||||||
frameId: "child",
|
frameId: "child",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
export {
|
export {
|
||||||
type CaptureVomFrame,
|
|
||||||
type CaptureVomNode,
|
|
||||||
type CaptureVomObservationOptions,
|
type CaptureVomObservationOptions,
|
||||||
type CaptureVomObservationResult,
|
|
||||||
captureVomObservation,
|
captureVomObservation,
|
||||||
} from "./observation";
|
} from "./observation";
|
||||||
|
export {
|
||||||
|
type CaptureVomFrame,
|
||||||
|
type CaptureVomMatchNode,
|
||||||
|
type CaptureVomObservationResult,
|
||||||
|
} from "./vom/record-safe-observation";
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import {
|
|||||||
applyVomInteractionRecovery,
|
applyVomInteractionRecovery,
|
||||||
type CondSurface,
|
type CondSurface,
|
||||||
isVomReferenceNode,
|
isVomReferenceNode,
|
||||||
type Rect,
|
|
||||||
type RenderedRef,
|
|
||||||
renderVom,
|
renderVom,
|
||||||
type VomNode,
|
type VomNode,
|
||||||
type VomOptions,
|
type VomOptions,
|
||||||
@@ -51,12 +49,15 @@ import {
|
|||||||
import { resolveSnapshotRef } from "./snapshot-ref";
|
import { resolveSnapshotRef } from "./snapshot-ref";
|
||||||
import {
|
import {
|
||||||
type CapturedNode,
|
type CapturedNode,
|
||||||
type CapturedSurfaceProbe,
|
|
||||||
type CapturedViewModel,
|
type CapturedViewModel,
|
||||||
captureViewModel,
|
captureViewModel,
|
||||||
collectOverlayExcludedBackendIds,
|
collectOverlayExcludedBackendIds,
|
||||||
} from "./vom/capture";
|
} from "./vom/capture";
|
||||||
import { type CapturedFrameDocument, captureFrameData } from "./vom/frame-capture";
|
import { type CapturedFrameDocument, captureFrameData } from "./vom/frame-capture";
|
||||||
|
import {
|
||||||
|
type CaptureVomObservationResult,
|
||||||
|
projectRecordSafeObservation,
|
||||||
|
} from "./vom/record-safe-observation";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Shared helpers (legacy aliases — observation.ts kept exporting these
|
// Shared helpers (legacy aliases — observation.ts kept exporting these
|
||||||
@@ -1591,58 +1592,6 @@ export interface CaptureVomObservationOptions extends VomOptions {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Frame identity needed to click an `@eN` that lives in an iframe. */
|
|
||||||
export interface CaptureVomFrame {
|
|
||||||
frameId: string;
|
|
||||||
target: CdpTarget;
|
|
||||||
parentFrameId?: string;
|
|
||||||
ownerBackendNodeId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Geometry index for matching a recorded click to a captured node. */
|
|
||||||
export interface CaptureVomNode {
|
|
||||||
frameId: string;
|
|
||||||
backendNodeId: number;
|
|
||||||
tag: string;
|
|
||||||
rect: Rect | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record-safe observation: rendered VOM text plus the matching index.
|
|
||||||
* Raw AX/DOM trees are not returned — they carry form values.
|
|
||||||
*/
|
|
||||||
export interface CaptureVomObservationResult {
|
|
||||||
text: string;
|
|
||||||
refs: RenderedRef[];
|
|
||||||
truncated: boolean;
|
|
||||||
rootFrameId: string;
|
|
||||||
frames: CaptureVomFrame[];
|
|
||||||
nodes: CaptureVomNode[];
|
|
||||||
surfaceProbes?: CapturedSurfaceProbe[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function recordSafeFrames(documents: CapturedFrameDocument<CdpAxNode>[]): CaptureVomFrame[] {
|
|
||||||
return documents.map((document) => ({
|
|
||||||
frameId: document.frameId,
|
|
||||||
target: document.target,
|
|
||||||
...(document.parentFrameId ? { parentFrameId: document.parentFrameId } : {}),
|
|
||||||
...(document.ownerBackendNodeId !== undefined
|
|
||||||
? { ownerBackendNodeId: document.ownerBackendNodeId }
|
|
||||||
: {}),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function recordSafeNodes(documents: CapturedFrameDocument<CdpAxNode>[]): CaptureVomNode[] {
|
|
||||||
return documents.flatMap((document) =>
|
|
||||||
document.domNodes.map((node) => ({
|
|
||||||
frameId: document.frameId,
|
|
||||||
backendNodeId: node.backendNodeId,
|
|
||||||
tag: node.tag,
|
|
||||||
rect: node.rect,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function captureVomObservation(
|
export async function captureVomObservation(
|
||||||
cdp: CdpRunner,
|
cdp: CdpRunner,
|
||||||
tabId: number,
|
tabId: number,
|
||||||
@@ -1667,15 +1616,12 @@ export async function captureVomObservation(
|
|||||||
activeRegionPolicy: options.activeRegionPolicy,
|
activeRegionPolicy: options.activeRegionPolicy,
|
||||||
});
|
});
|
||||||
throwIfAborted(options.signal, "observation");
|
throwIfAborted(options.signal, "observation");
|
||||||
return {
|
return projectRecordSafeObservation({
|
||||||
text: rendered.text,
|
|
||||||
refs: rendered.refs,
|
|
||||||
truncated: rendered.truncated,
|
|
||||||
rootFrameId: captured.rootFrameId ?? documents[0]?.frameId ?? "root",
|
rootFrameId: captured.rootFrameId ?? documents[0]?.frameId ?? "root",
|
||||||
frames: recordSafeFrames(documents),
|
frameDocuments: documents,
|
||||||
nodes: recordSafeNodes(documents),
|
rendered,
|
||||||
surfaceProbes: captured.surfaceProbes,
|
surfaceProbes: captured.surfaceProbes,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleVomObservation(
|
async function handleVomObservation(
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import type { Rect, RenderedRef, VomResult } from "@browser-skill/vom";
|
||||||
|
import type { CdpTarget } from "@/browser-driver/frame-graph";
|
||||||
|
import type { CapturedSurfaceProbe } from "./capture";
|
||||||
|
import type { CapturedFrameDocument, FrameAxNode } from "./frame-capture";
|
||||||
|
|
||||||
|
/** Frame identity needed to resolve an `@eN` that lives in an iframe. */
|
||||||
|
export interface CaptureVomFrame {
|
||||||
|
frameId: string;
|
||||||
|
target: CdpTarget;
|
||||||
|
parentFrameId?: string;
|
||||||
|
ownerBackendNodeId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Allowlisted geometry used to match a recorded action to a rendered ref. */
|
||||||
|
export interface CaptureVomMatchNode {
|
||||||
|
frameId: string;
|
||||||
|
backendNodeId: number;
|
||||||
|
tag: string;
|
||||||
|
/** Top-level viewport-relative geometry. */
|
||||||
|
rect: Rect | null;
|
||||||
|
/** Frame-local viewport-relative geometry, when available. */
|
||||||
|
localRect?: Rect | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw AX/DOM trees are never returned. When `redactValues` is enabled by the
|
||||||
|
* caller, form values in `text` are masked as well.
|
||||||
|
*/
|
||||||
|
export interface CaptureVomObservationResult {
|
||||||
|
text: string;
|
||||||
|
refs: RenderedRef[];
|
||||||
|
truncated: boolean;
|
||||||
|
rootFrameId: string;
|
||||||
|
frames: CaptureVomFrame[];
|
||||||
|
matchNodes: CaptureVomMatchNode[];
|
||||||
|
surfaceProbes?: CapturedSurfaceProbe[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectFrames(documents: CapturedFrameDocument<FrameAxNode>[]): CaptureVomFrame[] {
|
||||||
|
return documents.map((document) => ({
|
||||||
|
frameId: document.frameId,
|
||||||
|
target: document.target,
|
||||||
|
...(document.parentFrameId ? { parentFrameId: document.parentFrameId } : {}),
|
||||||
|
...(document.ownerBackendNodeId !== undefined
|
||||||
|
? { ownerBackendNodeId: document.ownerBackendNodeId }
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectMatchNodes(documents: CapturedFrameDocument<FrameAxNode>[]): CaptureVomMatchNode[] {
|
||||||
|
return documents.flatMap((document) =>
|
||||||
|
document.domNodes.map((node) => ({
|
||||||
|
frameId: document.frameId,
|
||||||
|
backendNodeId: node.backendNodeId,
|
||||||
|
tag: node.tag,
|
||||||
|
rect: node.rect,
|
||||||
|
...(node.localRect !== undefined ? { localRect: node.localRect } : {}),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectRecordSafeObservation(input: {
|
||||||
|
rootFrameId: string;
|
||||||
|
frameDocuments: CapturedFrameDocument<FrameAxNode>[];
|
||||||
|
rendered: VomResult;
|
||||||
|
surfaceProbes?: CapturedSurfaceProbe[];
|
||||||
|
}): CaptureVomObservationResult {
|
||||||
|
return {
|
||||||
|
text: input.rendered.text,
|
||||||
|
refs: input.rendered.refs,
|
||||||
|
truncated: input.rendered.truncated,
|
||||||
|
rootFrameId: input.rootFrameId,
|
||||||
|
frames: projectFrames(input.frameDocuments),
|
||||||
|
matchNodes: projectMatchNodes(input.frameDocuments),
|
||||||
|
surfaceProbes: input.surfaceProbes,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user