Skip to content

Commit e01a8a3

Browse files
authored
Merge pull request #163 from SentienceAPI/scaffold_captcha
Pagecontrol hook for evaluateJs
2 parents dfad8be + e264c88 commit e01a8a3

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

src/agent-runtime.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,13 @@ export class AgentRuntime {
809809
url: snapshot.url,
810810
source,
811811
captcha: snapshot.diagnostics?.captcha ?? null,
812+
evaluateJs: async (code: string) => {
813+
const result = await this.evaluateJs({ code });
814+
if (!result.ok) {
815+
throw new Error(result.error ?? 'evaluateJs failed');
816+
}
817+
return result.value;
818+
},
812819
};
813820
}
814821

src/captcha/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface CaptchaContext {
1616
snapshotPath?: string;
1717
liveSessionUrl?: string;
1818
meta?: Record<string, string>;
19+
evaluateJs?: (code: string) => Promise<any>;
20+
pageControl?: PageControlHook;
1921
}
2022

2123
export interface CaptchaResolution {
@@ -26,6 +28,11 @@ export interface CaptchaResolution {
2628
pollMs?: number;
2729
}
2830

31+
export interface PageControlHook {
32+
evaluateJs: (code: string) => Promise<any>;
33+
getUrl?: () => Promise<string>;
34+
}
35+
2936
export type CaptchaHandler = (
3037
ctx: CaptchaContext
3138
) => CaptchaResolution | Promise<CaptchaResolution>;

src/extension/injected_api.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,26 @@
115115
arkose: 0,
116116
awswaf: 0
117117
};
118+
function isVisibleElement(el) {
119+
try {
120+
if (!el) return !1;
121+
const style = window.getComputedStyle(el);
122+
if ("none" === style.display || "hidden" === style.visibility) return !1;
123+
const opacity = parseFloat(style.opacity || "1");
124+
if (!Number.isNaN(opacity) && opacity <= .01) return !1;
125+
if (!el.getClientRects || 0 === el.getClientRects().length) return !1;
126+
const rect = el.getBoundingClientRect();
127+
if (rect.width < 8 || rect.height < 8) return !1;
128+
const vw = window.innerWidth || document.documentElement.clientWidth || 0;
129+
const vh = window.innerHeight || document.documentElement.clientHeight || 0;
130+
if (vw && vh) {
131+
if (rect.bottom <= 0 || rect.right <= 0 || rect.top >= vh || rect.left >= vw) return !1;
132+
}
133+
return !0;
134+
} catch (e) {
135+
return !1;
136+
}
137+
}
118138
try {
119139
const iframes = document.querySelectorAll("iframe");
120140
for (const iframe of iframes) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { AgentRuntime } from '../src/agent-runtime';
2+
import { Tracer } from '../src/tracing/tracer';
3+
import { TraceSink } from '../src/tracing/sink';
4+
import { CaptchaDiagnostics, Snapshot } from '../src/types';
5+
import { MockPage } from './mocks/browser-mock';
6+
7+
class MockSink extends TraceSink {
8+
public events: any[] = [];
9+
emit(event: Record<string, any>): void {
10+
this.events.push(event);
11+
}
12+
async close(): Promise<void> {
13+
// no-op
14+
}
15+
getSinkType(): string {
16+
return 'MockSink';
17+
}
18+
}
19+
20+
describe('AgentRuntime captcha context', () => {
21+
it('exposes evaluateJs hook to captcha handlers', async () => {
22+
const sink = new MockSink();
23+
const tracer = new Tracer('test-run', sink);
24+
const page = new MockPage('https://example.com') as any;
25+
page.evaluate = jest.fn().mockResolvedValue('ok');
26+
27+
const captcha: CaptchaDiagnostics = {
28+
detected: true,
29+
confidence: 0.9,
30+
provider_hint: 'recaptcha',
31+
evidence: {
32+
iframe_src_hits: ['https://www.google.com/recaptcha/api2/anchor'],
33+
selector_hits: [],
34+
text_hits: [],
35+
url_hits: [],
36+
},
37+
};
38+
39+
const snapshot: Snapshot = {
40+
status: 'success',
41+
url: 'https://example.com',
42+
elements: [],
43+
diagnostics: { captcha },
44+
timestamp: 't1',
45+
};
46+
47+
const browserLike = {
48+
snapshot: async () => snapshot,
49+
};
50+
51+
const runtime = new AgentRuntime(browserLike as any, page as any, tracer);
52+
runtime.beginStep('captcha_test');
53+
54+
const ctx = (runtime as any).buildCaptchaContext(snapshot, 'gateway');
55+
expect(typeof ctx.evaluateJs).toBe('function');
56+
57+
const result = await ctx.evaluateJs('1+1');
58+
expect(result).toBe('ok');
59+
expect(page.evaluate).toHaveBeenCalled();
60+
});
61+
});

0 commit comments

Comments
 (0)