Skip to content

Commit 347ac10

Browse files
Add per-call frame filter overrides across task and replay APIs
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent be7338a commit 347ac10

11 files changed

Lines changed: 224 additions & 36 deletions

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ await page.perform("click the login button", {
200200
- `retryDelayMs`: delay between element-refetch retries.
201201
- `maxContextSwitchRetries`: retries when a tab/context switch interrupts an in-flight action.
202202
- `contextSwitchRetryDelayMs`: delay between context-switch retries (defaults to 500ms, capped for safety).
203+
- `filterAdTrackingFrames`: override iframe filtering for this action (`true` by default). Set to `false` when you intentionally need ad/tracking iframes in scope.
203204
- `maxSteps` (**deprecated**): compatibility alias for `maxElementRetries`.
204205
- Using `maxSteps` emits a one-time deprecation warning per agent instance.
205206

@@ -218,6 +219,7 @@ await page.perform("click the login button", {
218219

219220
- `useDomCache` (boolean): Reuse DOM snapshots for speed
220221
- `enableVisualMode` (boolean): Enable screenshots and overlays (default: false)
222+
- `filterAdTrackingFrames` (boolean): override ad/tracking iframe filtering for this task (inherits agent-level default when omitted)
221223

222224
**Example**:
223225

@@ -573,6 +575,7 @@ HyperAgent integrates seamlessly with Playwright, so you can still use familiar
573575

574576
Keep in mind that CDP is still experimental, and stability is not guaranteed. If you'd like the agent to use Playwright's native locators/actions instead, set `cdpActions: false` when you create the agent and it will fall back automatically.
575577
If you need to inspect ad/tracking iframes for a specific workflow, keep CDP enabled and set `filterAdTrackingFrames: false` in `new HyperAgent({ ... })`.
578+
You can also override this per invocation using `page.ai(..., { filterAdTrackingFrames: false })` or `page.perform(..., { filterAdTrackingFrames: false })`.
576579

577580
The CDP layer is still evolving—expect rapid polish (and the occasional sharp edge). If you hit something quirky you can toggle CDP off for that workflow and drop us a bug report.
578581

‎currentState.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ HyperAgent exposes a TypeScript SDK for browser automation with three primary pa
118118
- Removed direct `as any` casts in OpenAI/Anthropic/DeepSeek/Gemini provider request payload assembly in favor of SDK-derived parameter field typing.
119119
- Added `filterAdTrackingFrames` configuration in `HyperAgent` so CDP frame discovery can optionally include ad/tracking iframes for workflows that require them.
120120
- Propagated frame-filter policy through DOM-settle/replay/wait paths so `waitForSettledDOM` and special replay actions honor per-agent ad/tracking frame filtering settings.
121+
- Added per-invocation frame-filter overrides on `page.ai`, `page.perform`, and replay params so workflows can opt in/out of ad/tracking iframe filtering without constructing a new agent.
121122
- Expanded top-level package exports for key workflow/config types at `@hyperbrowser/agent`.
122123
- Removed stale script entry (`build-dom-tree-script`) and improved README usage docs.
123124
- Added canonical single-action debug writer helper (`writePerformDebug`) while preserving deprecated alias compatibility.

‎docs/cdp-overview.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ const agent = new HyperAgent({
2626
- `true` (default): skip likely ad/tracking frames during OOPIF/session discovery.
2727
- `false`: include those frames, which is useful for workflows that intentionally interact with ad-tech, analytics tags, or embedded consent/tracking widgets.
2828

29+
Override scopes:
30+
- agent-level default via `new HyperAgent({ filterAdTrackingFrames: ... })`,
31+
- per-task override via `page.ai(task, { filterAdTrackingFrames: ... })`,
32+
- per-single-action override via `page.perform(instruction, { filterAdTrackingFrames: ... })`,
33+
- replay override via `runFromActionCache(..., { filterAdTrackingFrames: ... })`.
34+
2935
The same setting is propagated through:
3036
- task loop runtime context initialization (`page.ai()` flow),
3137
- single-action execution (`page.perform()` flow),

‎src/__tests__/public-api.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ describe("public API exports", () => {
6363
retryDelayMs: 250,
6464
maxContextSwitchRetries: 4,
6565
contextSwitchRetryDelayMs: 500,
66+
filterAdTrackingFrames: false,
6667
};
6768

6869
expect(performParams.contextSwitchRetryDelayMs).toBe(500);
70+
expect(performParams.filterAdTrackingFrames).toBe(false);
6971
});
7072

7173
it("exposes frame-filter configuration on public HyperAgentConfig", () => {
@@ -75,4 +77,16 @@ describe("public API exports", () => {
7577

7678
expect(config.filterAdTrackingFrames).toBe(false);
7779
});
80+
81+
it("exposes frame-filter overrides on task and replay params", () => {
82+
const taskParams: TaskParams = {
83+
filterAdTrackingFrames: false,
84+
};
85+
const replayParams: RunFromActionCacheParams = {
86+
filterAdTrackingFrames: false,
87+
};
88+
89+
expect(taskParams.filterAdTrackingFrames).toBe(false);
90+
expect(replayParams.filterAdTrackingFrames).toBe(false);
91+
});
7892
});

‎src/agent/__tests__/execute-single-action.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,31 @@ describe("HyperAgent.executeSingleAction retry options", () => {
167167
);
168168
});
169169

170+
it("prefers per-call filterAdTrackingFrames override over agent default", async () => {
171+
const agent = new HyperAgent({
172+
llm: createMockLLM(),
173+
debug: false,
174+
cdpActions: false,
175+
filterAdTrackingFrames: true,
176+
});
177+
const page = {
178+
url: () => "https://example.com",
179+
} as unknown as Page;
180+
181+
await agent.executeSingleAction("click login", page, {
182+
filterAdTrackingFrames: false,
183+
});
184+
185+
expect(findElementWithInstruction).toHaveBeenCalledWith(
186+
"click login",
187+
page,
188+
expect.any(Object),
189+
expect.objectContaining({
190+
filterAdTrackingFrames: false,
191+
})
192+
);
193+
});
194+
170195
it("uses deprecated maxSteps as fallback for single-action retries", async () => {
171196
const agent = new HyperAgent({
172197
llm: createMockLLM(),

‎src/agent/__tests__/hyperagent-constructor.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,39 @@ describe("HyperAgent constructor and task controls", () => {
146146
expect(runtimeCtx?.filterAdTrackingFrames).toBe(false);
147147
});
148148

149+
it("prefers per-task filterAdTrackingFrames override over agent default", async () => {
150+
const mockedRunAgentTask = jest.mocked(runAgentTask);
151+
mockedRunAgentTask.mockResolvedValue({
152+
taskId: "task-id-filter-override",
153+
status: TaskStatus.COMPLETED,
154+
steps: [],
155+
output: "done",
156+
actionCache: {
157+
taskId: "task-id-filter-override",
158+
createdAt: new Date().toISOString(),
159+
status: TaskStatus.COMPLETED,
160+
steps: [],
161+
},
162+
});
163+
164+
const agent = new HyperAgent({
165+
llm: createMockLLM(),
166+
filterAdTrackingFrames: true,
167+
});
168+
const fakePage = {} as unknown as Page;
169+
170+
await agent.executeTask(
171+
"test task",
172+
{ filterAdTrackingFrames: false },
173+
fakePage
174+
);
175+
176+
const runtimeCtx = mockedRunAgentTask.mock.calls[0]?.[0] as {
177+
filterAdTrackingFrames?: boolean;
178+
};
179+
expect(runtimeCtx?.filterAdTrackingFrames).toBe(false);
180+
});
181+
149182
it("executeTaskAsync cleans up state when setup throws before run starts", async () => {
150183
const agent = new HyperAgent({
151184
llm: createMockLLM(),

‎src/agent/__tests__/run-from-action-cache.test.ts‎

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ describe("runFromActionCache hardening", () => {
6666

6767
const replay = await agent.runFromActionCache(cache, page);
6868

69-
expect(perform).toHaveBeenCalledWith("click login");
69+
expect(perform).toHaveBeenCalledWith(
70+
"click login",
71+
expect.objectContaining({
72+
filterAdTrackingFrames: true,
73+
})
74+
);
7075
expect(performClick).not.toHaveBeenCalled();
7176
expect(replay.status).toBe(TaskStatus.COMPLETED);
7277
expect(replay.steps[0]?.usedXPath).toBe(false);
@@ -1218,8 +1223,20 @@ describe("runFromActionCache hardening", () => {
12181223
const replay = await agent.runFromActionCache(cache, page);
12191224

12201225
expect(replay.status).toBe(TaskStatus.COMPLETED);
1221-
expect(perform).toHaveBeenNthCalledWith(1, "normal step");
1222-
expect(perform).toHaveBeenNthCalledWith(2, "nan index step");
1226+
expect(perform).toHaveBeenNthCalledWith(
1227+
1,
1228+
"normal step",
1229+
expect.objectContaining({
1230+
filterAdTrackingFrames: true,
1231+
})
1232+
);
1233+
expect(perform).toHaveBeenNthCalledWith(
1234+
2,
1235+
"nan index step",
1236+
expect.objectContaining({
1237+
filterAdTrackingFrames: true,
1238+
})
1239+
);
12231240
expect(replay.steps[0]?.stepIndex).toBe(0);
12241241
expect(replay.steps[1]?.stepIndex).toBe(-1);
12251242
});
@@ -1291,7 +1308,63 @@ describe("runFromActionCache hardening", () => {
12911308

12921309
expect(replay.sourceTaskId).toBe("unknown-task");
12931310
expect(replay.status).toBe(TaskStatus.COMPLETED);
1294-
expect(perform).toHaveBeenCalledWith("fallback source id");
1311+
expect(perform).toHaveBeenCalledWith(
1312+
"fallback source id",
1313+
expect.objectContaining({
1314+
filterAdTrackingFrames: true,
1315+
})
1316+
);
1317+
});
1318+
1319+
it("applies runFromActionCache filter override to perform fallback calls", async () => {
1320+
const agent = new HyperAgent({
1321+
llm: createMockLLM(),
1322+
cdpActions: false,
1323+
});
1324+
const perform = jest.fn().mockResolvedValue({
1325+
taskId: "perform-task",
1326+
status: TaskStatus.COMPLETED,
1327+
steps: [],
1328+
output: "performed via instruction",
1329+
replayStepMeta: {
1330+
usedCachedAction: false,
1331+
fallbackUsed: true,
1332+
retries: 1,
1333+
},
1334+
});
1335+
const page = {
1336+
perform,
1337+
} as unknown as import("@/types/agent/types").HyperPage;
1338+
const cache: ActionCacheOutput = {
1339+
taskId: "cache-task",
1340+
createdAt: new Date().toISOString(),
1341+
status: TaskStatus.COMPLETED,
1342+
steps: [
1343+
{
1344+
stepIndex: 0,
1345+
instruction: "click ad iframe CTA",
1346+
elementId: "0-1",
1347+
method: "click",
1348+
arguments: [],
1349+
frameIndex: 0,
1350+
xpath: null,
1351+
actionType: "actElement",
1352+
success: true,
1353+
message: "cached",
1354+
},
1355+
],
1356+
};
1357+
1358+
await agent.runFromActionCache(cache, page, {
1359+
filterAdTrackingFrames: false,
1360+
});
1361+
1362+
expect(perform).toHaveBeenCalledWith(
1363+
"click ad iframe CTA",
1364+
expect.objectContaining({
1365+
filterAdTrackingFrames: false,
1366+
})
1367+
);
12951368
});
12961369

12971370
it("truncates oversized cached-step read diagnostics", async () => {

0 commit comments

Comments
 (0)