hyper-agent-browser 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyper-agent-browser",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Pure browser automation CLI for AI Agents - 纯浏览器自动化 CLI,专为 AI Agent 设计",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -149,14 +149,23 @@ export class BrowserManager {
149
149
  }
150
150
 
151
151
  // Use launchPersistentContext for UserData persistence
152
- this.context = await chromium.launchPersistentContext(this.session.userDataDir, {
152
+ // 给启动加上超时保护(15秒)
153
+ const launchPromise = chromium.launchPersistentContext(this.session.userDataDir, {
153
154
  channel: this.options.channel,
154
155
  headless: !this.options.headed,
155
156
  args: launchArgs,
156
157
  ignoreDefaultArgs: ignoreArgs,
157
158
  viewport: { width: 1280, height: 720 },
159
+ timeout: 15000, // 15秒启动超时
158
160
  });
159
161
 
162
+ this.context = await Promise.race([
163
+ launchPromise,
164
+ new Promise<never>((_, reject) =>
165
+ setTimeout(() => reject(new Error("Browser launch timeout (15s)")), 15000),
166
+ ),
167
+ ]);
168
+
160
169
  // Extract browser from context
161
170
  // @ts-ignore - context has _browser property
162
171
  this.browser = this.context._browser;
@@ -334,6 +343,22 @@ export class BrowserManager {
334
343
  // 重新连接
335
344
  await this.connect();
336
345
  }
346
+
347
+ // 确保返回当前活动页面(可能有多个页面时需要获取最新的)
348
+ if (this.context) {
349
+ const pages = this.context.pages();
350
+ if (pages.length > 0) {
351
+ // 优先返回非 about:blank 的页面
352
+ const activePage = pages.find((p) => p.url() !== "about:blank") || pages[pages.length - 1];
353
+ if (activePage !== this.page) {
354
+ this.page = activePage;
355
+ if (this.options.timeout) {
356
+ this.page.setDefaultTimeout(this.options.timeout);
357
+ }
358
+ }
359
+ }
360
+ }
361
+
337
362
  return this.page!;
338
363
  }
339
364
 
@@ -353,7 +378,30 @@ export class BrowserManager {
353
378
 
354
379
  async close(): Promise<void> {
355
380
  if (this.browser) {
356
- await this.browser.close();
381
+ // 获取 PID 以便超时后强制 kill
382
+ const pid = this.getPid();
383
+
384
+ try {
385
+ // 给 close 操作 5 秒超时
386
+ await Promise.race([
387
+ this.browser.close(),
388
+ new Promise((_, reject) =>
389
+ setTimeout(() => reject(new Error("Browser close timeout")), 5000),
390
+ ),
391
+ ]);
392
+ } catch (error) {
393
+ console.log("Browser close failed or timed out, forcing cleanup...");
394
+ // 强制 kill 进程
395
+ if (pid) {
396
+ try {
397
+ process.kill(pid, "SIGKILL");
398
+ console.log(`Force killed browser process (PID: ${pid})`);
399
+ } catch {
400
+ // 进程可能已经退出
401
+ }
402
+ }
403
+ }
404
+
357
405
  this.browser = null;
358
406
  this.context = null;
359
407
  this.page = null;
@@ -43,7 +43,42 @@ async function getLocator(page: Page, selector: string): Promise<Locator> {
43
43
 
44
44
  export async function click(page: Page, selector: string): Promise<void> {
45
45
  const locator = await getLocator(page, selector);
46
- await locator.click();
46
+
47
+ // 先尝试正常点击
48
+ try {
49
+ await locator.click({ timeout: 5000 });
50
+ } catch (error) {
51
+ // 如果被遮罩拦截,逐级降级
52
+ if (error instanceof Error && error.message.includes("intercepts pointer events")) {
53
+ console.log("Element intercepted, trying force click...");
54
+ try {
55
+ await locator.click({ force: true, timeout: 5000 });
56
+ } catch {
57
+ // force click 失败,使用完整鼠标事件序列(兼容 React 等框架)
58
+ console.log("Force click failed, using mouse event sequence...");
59
+ await locator.evaluate((el: HTMLElement) => {
60
+ const rect = el.getBoundingClientRect();
61
+ const x = rect.left + rect.width / 2;
62
+ const y = rect.top + rect.height / 2;
63
+
64
+ // 模拟完整鼠标事件序列
65
+ for (const type of ["mousedown", "mouseup", "click"]) {
66
+ el.dispatchEvent(
67
+ new MouseEvent(type, {
68
+ bubbles: true,
69
+ cancelable: true,
70
+ view: window,
71
+ clientX: x,
72
+ clientY: y,
73
+ }),
74
+ );
75
+ }
76
+ });
77
+ }
78
+ } else {
79
+ throw error;
80
+ }
81
+ }
47
82
  }
48
83
 
49
84
  export async function fill(page: Page, selector: string, value: string): Promise<void> {