mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
Fix Biome and Stylelint formatting so pnpm lint passes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from "@browser-skill/i18n/react";
|
import { useTranslation } from "@browser-skill/i18n/react";
|
||||||
import { RiCloseLine } from "@remixicon/react";
|
import { RiCloseLine } from "@remixicon/react";
|
||||||
import { useEffect, useRef, useState, type TransitionEvent } from "react";
|
import { type TransitionEvent, useEffect, useRef, useState } from "react";
|
||||||
import logoUrl from "../../assets/logo.png";
|
import logoUrl from "../../assets/logo.png";
|
||||||
|
|
||||||
export interface BorrowRequestData {
|
export interface BorrowRequestData {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useTranslation } from "@browser-skill/i18n/react";
|
import { useTranslation } from "@browser-skill/i18n/react";
|
||||||
import { RiArrowDownSLine } from "@remixicon/react";
|
import { RiArrowDownSLine } from "@remixicon/react";
|
||||||
import {
|
import {
|
||||||
|
type PointerEvent as ReactPointerEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
type PointerEvent as ReactPointerEvent,
|
|
||||||
} from "react";
|
} from "react";
|
||||||
import logoUrl from "../../assets/logo.png";
|
import logoUrl from "../../assets/logo.png";
|
||||||
|
|
||||||
@@ -239,25 +239,29 @@ export function HelpRequestOverlay({ request }: Props) {
|
|||||||
};
|
};
|
||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
const onHeaderPointerDown = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {
|
const onHeaderPointerDown = useCallback(
|
||||||
if (e.button !== 0) return;
|
(e: ReactPointerEvent<HTMLDivElement>) => {
|
||||||
if (e.target instanceof Element && e.target.closest("button, input, textarea, a, select")) return;
|
if (e.button !== 0) return;
|
||||||
const banner = bannerRef.current;
|
if (e.target instanceof Element && e.target.closest("button, input, textarea, a, select"))
|
||||||
if (!banner) return;
|
return;
|
||||||
const rect = banner.getBoundingClientRect();
|
const banner = bannerRef.current;
|
||||||
dragStartRef.current = {
|
if (!banner) return;
|
||||||
pointerX: e.clientX,
|
const rect = banner.getBoundingClientRect();
|
||||||
pointerY: e.clientY,
|
dragStartRef.current = {
|
||||||
originLeft: rect.left,
|
pointerX: e.clientX,
|
||||||
originTop: rect.top,
|
pointerY: e.clientY,
|
||||||
panelW: banner.offsetWidth || PANEL_WIDTH,
|
originLeft: rect.left,
|
||||||
panelH: banner.offsetHeight || (collapsed ? FALLBACK_PANEL_H_COLLAPSED : FALLBACK_PANEL_H),
|
originTop: rect.top,
|
||||||
};
|
panelW: banner.offsetWidth || PANEL_WIDTH,
|
||||||
userMovedRef.current = true;
|
panelH: banner.offsetHeight || (collapsed ? FALLBACK_PANEL_H_COLLAPSED : FALLBACK_PANEL_H),
|
||||||
setDragPos({ top: rect.top, left: rect.left });
|
};
|
||||||
setDragging(true);
|
userMovedRef.current = true;
|
||||||
e.preventDefault();
|
setDragPos({ top: rect.top, left: rect.left });
|
||||||
}, [collapsed]);
|
setDragging(true);
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
[collapsed],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dragging) return;
|
if (!dragging) return;
|
||||||
@@ -636,7 +640,11 @@ export function HelpRequestOverlay({ request }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bsk-help-header">
|
<div className="bsk-help-header">
|
||||||
<img src={logoUrl} alt="browser-skill" style={{ width: 22, height: 22, borderRadius: 4 }} />
|
<img
|
||||||
|
src={logoUrl}
|
||||||
|
alt="browser-skill"
|
||||||
|
style={{ width: 22, height: 22, borderRadius: 4 }}
|
||||||
|
/>
|
||||||
<span className="bsk-help-title">{request.title ?? t("helpRequest.title")}</span>
|
<span className="bsk-help-title">{request.title ?? t("helpRequest.title")}</span>
|
||||||
<div className="bsk-help-header-actions" aria-hidden={!collapsed}>
|
<div className="bsk-help-header-actions" aria-hidden={!collapsed}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { I18nextProvider } from "@browser-skill/i18n/react";
|
|||||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { HelpRequestOverlay, type HelpRequestData } from "../HelpRequestOverlay";
|
import { type HelpRequestData, HelpRequestOverlay } from "../HelpRequestOverlay";
|
||||||
|
|
||||||
function renderOverlay(req: HelpRequestData) {
|
function renderOverlay(req: HelpRequestData) {
|
||||||
return render(
|
return render(
|
||||||
@@ -79,7 +79,7 @@ describe("HelpRequestOverlay", () => {
|
|||||||
renderOverlay(baseRequest({ selectors: ["#login", "#missing"] }));
|
renderOverlay(baseRequest({ selectors: ["#login", "#missing"] }));
|
||||||
// One highlight box rendered for the matched selector.
|
// One highlight box rendered for the matched selector.
|
||||||
expect(document.querySelectorAll("[data-slot='help-highlight']").length).toBe(1);
|
expect(document.querySelectorAll("[data-slot='help-highlight']").length).toBe(1);
|
||||||
expect((el.scrollIntoView as ReturnType<typeof vi.fn>)).toHaveBeenCalled();
|
expect(el.scrollIntoView as ReturnType<typeof vi.fn>).toHaveBeenCalled();
|
||||||
el.remove();
|
el.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,8 +115,8 @@ describe("HelpRequestOverlay", () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect((elA.scrollIntoView as ReturnType<typeof vi.fn>)).toHaveBeenCalled();
|
expect(elA.scrollIntoView as ReturnType<typeof vi.fn>).toHaveBeenCalled();
|
||||||
expect((elB.scrollIntoView as ReturnType<typeof vi.fn>)).toHaveBeenCalled();
|
expect(elB.scrollIntoView as ReturnType<typeof vi.fn>).toHaveBeenCalled();
|
||||||
elA.remove();
|
elA.remove();
|
||||||
elB.remove();
|
elB.remove();
|
||||||
});
|
});
|
||||||
@@ -286,9 +286,7 @@ describe("HelpRequestOverlay", () => {
|
|||||||
it("does not start drag when pointer down on a button in the header", () => {
|
it("does not start drag when pointer down on a button in the header", () => {
|
||||||
const { container } = renderOverlay(baseRequest());
|
const { container } = renderOverlay(baseRequest());
|
||||||
const banner = container.querySelector("[data-slot='help-request-banner']") as HTMLElement;
|
const banner = container.querySelector("[data-slot='help-request-banner']") as HTMLElement;
|
||||||
const collapseBtn = screen.getByLabelText(
|
const collapseBtn = screen.getByLabelText(i18n.t("helpRequest.collapse", { ns: "extension" }));
|
||||||
i18n.t("helpRequest.collapse", { ns: "extension" }),
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent.pointerDown(collapseBtn, { button: 0, clientX: 10, clientY: 10, pointerId: 1 });
|
fireEvent.pointerDown(collapseBtn, { button: 0, clientX: 10, clientY: 10, pointerId: 1 });
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
// `background.ts` is a WXT entrypoint that calls the global `defineBackground`
|
// `background.ts` is a WXT entrypoint that calls the global `defineBackground`
|
||||||
// at module load. WXT injects that helper at build time via auto-imports;
|
// at module load. WXT injects that helper at build time via auto-imports;
|
||||||
|
|||||||
@@ -167,9 +167,9 @@ export default defineBackground(() => {
|
|||||||
// silently doing nothing (review M4/M5 C2).
|
// silently doing nothing (review M4/M5 C2).
|
||||||
console.warn("[browser-skill] set_port is not wired yet; ignoring", msg.value);
|
console.warn("[browser-skill] set_port is not wired yet; ignoring", msg.value);
|
||||||
} else if (msg.kind === "set_connection_enabled") {
|
} else if (msg.kind === "set_connection_enabled") {
|
||||||
void controller.setConnectionEnabled(msg.value).then(() =>
|
void controller
|
||||||
persistConnectionEnabled(msg.value),
|
.setConnectionEnabled(msg.value)
|
||||||
);
|
.then(() => persistConnectionEnabled(msg.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { i18n } from "@browser-skill/i18n";
|
||||||
|
import { I18nextProvider } from "@browser-skill/i18n/react";
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
import { BorrowConfirmationOverlay } from "@/content/BorrowConfirmationOverlay";
|
import { BorrowConfirmationOverlay } from "@/content/BorrowConfirmationOverlay";
|
||||||
import { ControlOverlay } from "@/content/ControlOverlay";
|
import { ControlOverlay } from "@/content/ControlOverlay";
|
||||||
import { HelpRequestOverlay } from "@/content/HelpRequestOverlay";
|
import { HelpRequestOverlay } from "@/content/HelpRequestOverlay";
|
||||||
@@ -12,12 +16,12 @@ import {
|
|||||||
isHelpRequestMessage,
|
isHelpRequestMessage,
|
||||||
} from "@/lib/help-bridge";
|
} from "@/lib/help-bridge";
|
||||||
import {
|
import {
|
||||||
|
isOverlayAgentOverlayResetMessage,
|
||||||
OVERLAY_AUTOMATION_BYPASS,
|
OVERLAY_AUTOMATION_BYPASS,
|
||||||
OVERLAY_MSG_WHO_AM_I,
|
OVERLAY_MSG_WHO_AM_I,
|
||||||
type OverlayAgentOverlayResetMessage,
|
type OverlayAgentOverlayResetMessage,
|
||||||
type OverlayAutomationBypassMessage,
|
type OverlayAutomationBypassMessage,
|
||||||
type OverlayWhoAmIResponse,
|
type OverlayWhoAmIResponse,
|
||||||
isOverlayAgentOverlayResetMessage,
|
|
||||||
} from "@/lib/overlay-bridge";
|
} from "@/lib/overlay-bridge";
|
||||||
import { sendInterrupt } from "@/lib/overlay-interrupt-client";
|
import { sendInterrupt } from "@/lib/overlay-interrupt-client";
|
||||||
import { SESSIONS_LIVE_FLAG_KEY } from "@/lib/sessions-live-flag";
|
import { SESSIONS_LIVE_FLAG_KEY } from "@/lib/sessions-live-flag";
|
||||||
@@ -26,10 +30,6 @@ import type {
|
|||||||
BorrowRequestMessage,
|
BorrowRequestMessage,
|
||||||
BorrowResponseMessage,
|
BorrowResponseMessage,
|
||||||
} from "@/tools/borrow-confirmation";
|
} from "@/tools/borrow-confirmation";
|
||||||
import { i18n } from "@browser-skill/i18n";
|
|
||||||
import { I18nextProvider } from "@browser-skill/i18n/react";
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
|
|
||||||
// Run at document_end so the overlay does not block first paint. Only attach
|
// Run at document_end so the overlay does not block first paint. Only attach
|
||||||
// in the top-level frame so iframes do not double-render overlays.
|
// in the top-level frame so iframes do not double-render overlays.
|
||||||
@@ -117,10 +117,7 @@ export default defineContentScript({
|
|||||||
}
|
}
|
||||||
overlays.setInterrupting(true);
|
overlays.setInterrupting(true);
|
||||||
renderOverlay();
|
renderOverlay();
|
||||||
void sendInterrupt(
|
void sendInterrupt((msg) => chrome.runtime.sendMessage(msg), sessionId).then((reply) => {
|
||||||
(msg) => chrome.runtime.sendMessage(msg),
|
|
||||||
sessionId,
|
|
||||||
).then((reply) => {
|
|
||||||
// Always retract the mask after the round trip resolves
|
// Always retract the mask after the round trip resolves
|
||||||
// (success, failure, or timeout). Cancellation is fire-and-
|
// (success, failure, or timeout). Cancellation is fire-and-
|
||||||
// forget on the daemon side; the user must not be stuck
|
// forget on the daemon side; the user must not be stuck
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ describe("App", () => {
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
expect(screen.getByText("连接已关闭")).toBeTruthy();
|
expect(screen.getByText("连接已关闭")).toBeTruthy();
|
||||||
expect(screen.getByRole("switch", { name: "BrowserSkill 连接" }).getAttribute("aria-checked")).toBe(
|
expect(
|
||||||
"false",
|
screen.getByRole("switch", { name: "BrowserSkill 连接" }).getAttribute("aria-checked"),
|
||||||
);
|
).toBe("false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from "@browser-skill/i18n/react";
|
import { useTranslation } from "@browser-skill/i18n/react";
|
||||||
import { Badge, Button, Input, Label, cn } from "@browser-skill/ui";
|
import { Badge, Button, cn, Input, Label } from "@browser-skill/ui";
|
||||||
import { RiCheckLine, RiFileCopyLine } from "@remixicon/react";
|
import { RiCheckLine, RiFileCopyLine } from "@remixicon/react";
|
||||||
import { type ChangeEvent, useEffect, useState } from "react";
|
import { type ChangeEvent, useEffect, useState } from "react";
|
||||||
import { PROTOCOL_VERSION } from "@/transport/handshake";
|
import { PROTOCOL_VERSION } from "@/transport/handshake";
|
||||||
@@ -75,12 +75,7 @@ export function App() {
|
|||||||
data-version-skew={isSkewed ? "true" : undefined}
|
data-version-skew={isSkewed ? "true" : undefined}
|
||||||
>
|
>
|
||||||
<header className="flex items-center gap-2" data-slot="popup-brand-header">
|
<header className="flex items-center gap-2" data-slot="popup-brand-header">
|
||||||
<img
|
<img src={getLogoSrc()} alt="" className="size-7 rounded-lg" data-slot="popup-brand-logo" />
|
||||||
src={getLogoSrc()}
|
|
||||||
alt=""
|
|
||||||
className="size-7 rounded-lg"
|
|
||||||
data-slot="popup-brand-logo"
|
|
||||||
/>
|
|
||||||
<h1 className="text-sm font-semibold tracking-tight">{t("popup.brandName")}</h1>
|
<h1 className="text-sm font-semibold tracking-tight">{t("popup.brandName")}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -91,10 +86,7 @@ export function App() {
|
|||||||
<div className="flex items-center justify-between gap-2" data-slot="popup-status">
|
<div className="flex items-center justify-between gap-2" data-slot="popup-status">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<ConnectionStatusIndicator state={statusState} />
|
<ConnectionStatusIndicator state={statusState} />
|
||||||
<span
|
<span className="truncate text-sm font-medium" data-slot="popup-state-label">
|
||||||
className="truncate text-sm font-medium"
|
|
||||||
data-slot="popup-state-label"
|
|
||||||
>
|
|
||||||
{t(STATE_LABEL_KEYS[statusState])}
|
{t(STATE_LABEL_KEYS[statusState])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { ConnectionController, __testing__ } from "../connection-controller";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { Transport } from "../../transport/transport";
|
|
||||||
import type { ConnectionState } from "../../transport/types";
|
|
||||||
import type { ConnectionStateHandler } from "../../transport/transport";
|
|
||||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
||||||
import { MIN_COMPATIBLE_PROTOCOL } from "../../transport/handshake";
|
import { MIN_COMPATIBLE_PROTOCOL } from "../../transport/handshake";
|
||||||
import type { HandshakeResult } from "../../transport/types";
|
import type { ConnectionStateHandler, Transport } from "../../transport/transport";
|
||||||
|
import type { ConnectionState, HandshakeResult } from "../../transport/types";
|
||||||
|
import { __testing__, ConnectionController } from "../connection-controller";
|
||||||
|
|
||||||
vi.mock("../instance-id", () => ({
|
vi.mock("../instance-id", () => ({
|
||||||
getOrCreateInstanceId: vi.fn(async () => "a1b2c3d4"),
|
getOrCreateInstanceId: vi.fn(async () => "a1b2c3d4"),
|
||||||
|
|||||||
@@ -51,12 +51,8 @@ describe("help-bridge", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("type-guards a help-cancel message", () => {
|
it("type-guards a help-cancel message", () => {
|
||||||
expect(
|
expect(isHelpCancelMessage({ type: HELP_CANCEL, requestId: "r1" })).toBe(true);
|
||||||
isHelpCancelMessage({ type: HELP_CANCEL, requestId: "r1" }),
|
expect(isHelpCancelMessage({ type: HELP_REQUEST, requestId: "r1" })).toBe(false);
|
||||||
).toBe(true);
|
|
||||||
expect(isHelpCancelMessage({ type: HELP_REQUEST, requestId: "r1" })).toBe(
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
expect(isHelpCancelMessage(null)).toBe(false);
|
expect(isHelpCancelMessage(null)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
isOverlayAgentOverlayResetMessage,
|
||||||
OVERLAY_AGENT_OVERLAY_RESET,
|
OVERLAY_AGENT_OVERLAY_RESET,
|
||||||
OVERLAY_MSG_INTERRUPT,
|
OVERLAY_MSG_INTERRUPT,
|
||||||
type OverlayInterruptRequest,
|
type OverlayInterruptRequest,
|
||||||
type OverlayInterruptResponse,
|
type OverlayInterruptResponse,
|
||||||
isOverlayAgentOverlayResetMessage,
|
|
||||||
} from "@/lib/overlay-bridge";
|
} from "@/lib/overlay-bridge";
|
||||||
|
|
||||||
describe("OVERLAY_MSG_INTERRUPT", () => {
|
describe("OVERLAY_MSG_INTERRUPT", () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { sendInterrupt } from "@/lib/overlay-interrupt-client";
|
import { sendInterrupt } from "@/lib/overlay-interrupt-client";
|
||||||
|
|
||||||
describe("sendInterrupt", () => {
|
describe("sendInterrupt", () => {
|
||||||
|
|||||||
@@ -284,6 +284,9 @@ describe("handleEvaluate", () => {
|
|||||||
{ session_id: "aa11", expression: "1+1", tab_id: 11 },
|
{ session_id: "aa11", expression: "1+1", tab_id: 11 },
|
||||||
{ cdp: fake.cdp, tabsApi: fake.tabsApi },
|
{ cdp: fake.cdp, tabsApi: fake.tabsApi },
|
||||||
);
|
);
|
||||||
expect(res).toMatchObject({ code: "permission_denied", data: { reason: "agent_window_scope" } });
|
expect(res).toMatchObject({
|
||||||
|
code: "permission_denied",
|
||||||
|
data: { reason: "agent_window_scope" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,13 +36,21 @@ function baseDeps(over: Partial<RequestHelpDeps> = {}): RequestHelpDeps {
|
|||||||
|
|
||||||
describe("handleRequestHelp", () => {
|
describe("handleRequestHelp", () => {
|
||||||
it("rejects unknown session", async () => {
|
it("rejects unknown session", async () => {
|
||||||
const res = await handleRequestHelp(fakeManager("abcd", 99, 5), baseParams({ session_id: "zzzz" }), baseDeps());
|
const res = await handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ session_id: "zzzz" }),
|
||||||
|
baseDeps(),
|
||||||
|
);
|
||||||
expect("code" in res && res.code).toBe("not_found");
|
expect("code" in res && res.code).toBe("not_found");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("brings the tab to the foreground and returns the user outcome", async () => {
|
it("brings the tab to the foreground and returns the user outcome", async () => {
|
||||||
const deps = baseDeps();
|
const deps = baseDeps();
|
||||||
const res = await handleRequestHelp(fakeManager("abcd", 99, 5), baseParams({ tab_id: 5 }), deps);
|
const res = await handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ tab_id: 5 }),
|
||||||
|
deps,
|
||||||
|
);
|
||||||
expect(deps.windows.update).toHaveBeenCalledWith(99, { focused: true });
|
expect(deps.windows.update).toHaveBeenCalledWith(99, { focused: true });
|
||||||
expect(deps.activateTab).toHaveBeenCalledWith(5);
|
expect(deps.activateTab).toHaveBeenCalledWith(5);
|
||||||
expect(res).toMatchObject({ outcome: "continued", note: "ok", tab_id: 5 });
|
expect(res).toMatchObject({ outcome: "continued", note: "ok", tab_id: 5 });
|
||||||
@@ -79,7 +87,11 @@ describe("handleRequestHelp", () => {
|
|||||||
return unwatch;
|
return unwatch;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const res = await handleRequestHelp(fakeManager("abcd", 99, 5), baseParams({ tab_id: 5 }), deps);
|
const res = await handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ tab_id: 5 }),
|
||||||
|
deps,
|
||||||
|
);
|
||||||
expect(res).toMatchObject({ outcome: "navigated", tab_id: 5 });
|
expect(res).toMatchObject({ outcome: "navigated", tab_id: 5 });
|
||||||
expect(unwatch).toHaveBeenCalled();
|
expect(unwatch).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -107,12 +119,19 @@ describe("handleRequestHelp", () => {
|
|||||||
const mgr = {
|
const mgr = {
|
||||||
get: (id: string) =>
|
get: (id: string) =>
|
||||||
id === "abcd"
|
id === "abcd"
|
||||||
? { sessionId: "abcd", agentWindowId: 99, refStore: { resolve: () => 42 }, borrowedTabs: new Map() }
|
? {
|
||||||
|
sessionId: "abcd",
|
||||||
|
agentWindowId: 99,
|
||||||
|
refStore: { resolve: () => 42 },
|
||||||
|
borrowedTabs: new Map(),
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
findByWindowId: (wid: number) => (wid === 99 ? { sessionId: "abcd" } : null),
|
findByWindowId: (wid: number) => (wid === 99 ? { sessionId: "abcd" } : null),
|
||||||
} as unknown as SessionManager;
|
} as unknown as SessionManager;
|
||||||
const deps = baseDeps({
|
const deps = baseDeps({
|
||||||
cdp: { send: vi.fn(async () => ({ object: { objectId: "obj-1" } })) } as unknown as RequestHelpDeps["cdp"],
|
cdp: {
|
||||||
|
send: vi.fn(async () => ({ object: { objectId: "obj-1" } })),
|
||||||
|
} as unknown as RequestHelpDeps["cdp"],
|
||||||
});
|
});
|
||||||
const res = await handleRequestHelp(
|
const res = await handleRequestHelp(
|
||||||
mgr,
|
mgr,
|
||||||
@@ -177,7 +196,11 @@ describe("handleRequestHelp", () => {
|
|||||||
throw new Error("no receiver");
|
throw new Error("no receiver");
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const res = await handleRequestHelp(fakeManager("abcd", 99, 5), baseParams({ tab_id: 5 }), deps);
|
const res = await handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ tab_id: 5 }),
|
||||||
|
deps,
|
||||||
|
);
|
||||||
expect("code" in res && res.code).toBe("protocol_error");
|
expect("code" in res && res.code).toBe("protocol_error");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -254,7 +277,11 @@ describe("handleRequestHelp", () => {
|
|||||||
it("returns cancelled when the signal aborts", async () => {
|
it("returns cancelled when the signal aborts", async () => {
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const deps = baseDeps({ signal: ac.signal, sendToTab: vi.fn(() => new Promise(() => {})) });
|
const deps = baseDeps({ signal: ac.signal, sendToTab: vi.fn(() => new Promise(() => {})) });
|
||||||
const p = handleRequestHelp(fakeManager("abcd", 99, 5), baseParams({ tab_id: 5, timeout_ms: 60_000 }), deps);
|
const p = handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ tab_id: 5, timeout_ms: 60_000 }),
|
||||||
|
deps,
|
||||||
|
);
|
||||||
ac.abort();
|
ac.abort();
|
||||||
const res = await p;
|
const res = await p;
|
||||||
expect("code" in res ? res.code : (res as { outcome: string }).outcome).toMatch(/cancelled/);
|
expect("code" in res ? res.code : (res as { outcome: string }).outcome).toMatch(/cancelled/);
|
||||||
|
|||||||
@@ -982,4 +982,3 @@ describe("handleSelect", () => {
|
|||||||
expect(fake.sent.some((c) => c.method === "Runtime.callFunctionOn")).toBe(false);
|
expect(fake.sent.some((c) => c.method === "Runtime.callFunctionOn")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { SessionManager } from "@/session-manager/manager";
|
import { SessionManager } from "@/session-manager/manager";
|
||||||
import type { CdpRunner } from "@/tools/shared";
|
import type { CdpRunner } from "@/tools/shared";
|
||||||
import {
|
import {
|
||||||
|
eventLoaderIsRelevant,
|
||||||
handleNavigate,
|
handleNavigate,
|
||||||
handleNavigateBack,
|
handleNavigateBack,
|
||||||
handleNavigateForward,
|
handleNavigateForward,
|
||||||
handleReload,
|
handleReload,
|
||||||
lifecycleAlreadyReached,
|
lifecycleAlreadyReached,
|
||||||
eventLoaderIsRelevant,
|
|
||||||
lifecycleMeetsOrExceeds,
|
lifecycleMeetsOrExceeds,
|
||||||
shouldTrustReadyStateProbe,
|
shouldTrustReadyStateProbe,
|
||||||
} from "../navigation";
|
} from "../navigation";
|
||||||
@@ -60,10 +60,7 @@ function makeFakeCdp(opts?: {
|
|||||||
};
|
};
|
||||||
const events: EventListener[] = [];
|
const events: EventListener[] = [];
|
||||||
const sent: Array<{ tabId: number; method: string; params?: object }> = [];
|
const sent: Array<{ tabId: number; method: string; params?: object }> = [];
|
||||||
const methodHandlers: Record<
|
const methodHandlers: Record<string, (tabId: number, params?: object) => object> = {
|
||||||
string,
|
|
||||||
(tabId: number, params?: object) => object
|
|
||||||
> = {
|
|
||||||
"Page.enable": () => ({}),
|
"Page.enable": () => ({}),
|
||||||
"Page.setLifecycleEventsEnabled": () => ({}),
|
"Page.setLifecycleEventsEnabled": () => ({}),
|
||||||
"Page.navigate": () => {
|
"Page.navigate": () => {
|
||||||
@@ -72,9 +69,7 @@ function makeFakeCdp(opts?: {
|
|||||||
name: opts.fireLifecycleDuringNavigate,
|
name: opts.fireLifecycleDuringNavigate,
|
||||||
frameId: opts.fireLifecycleDuringNavigateFrameId ?? opts.navigateFrameId ?? "frame-1",
|
frameId: opts.fireLifecycleDuringNavigateFrameId ?? opts.navigateFrameId ?? "frame-1",
|
||||||
loaderId:
|
loaderId:
|
||||||
opts.fireLifecycleDuringNavigateLoaderId ??
|
opts.fireLifecycleDuringNavigateLoaderId ?? opts.navigateLoaderId ?? "loader-after",
|
||||||
opts.navigateLoaderId ??
|
|
||||||
"loader-after",
|
|
||||||
};
|
};
|
||||||
for (const listener of [...events]) {
|
for (const listener of [...events]) {
|
||||||
listener({ tabId: 4 }, "Page.lifecycleEvent", payload);
|
listener({ tabId: 4 }, "Page.lifecycleEvent", payload);
|
||||||
@@ -208,9 +203,7 @@ describe("lifecycleMeetsOrExceeds", () => {
|
|||||||
|
|
||||||
describe("eventLoaderIsRelevant", () => {
|
describe("eventLoaderIsRelevant", () => {
|
||||||
it("rejects events from the pre-navigation loader", () => {
|
it("rejects events from the pre-navigation loader", () => {
|
||||||
expect(
|
expect(eventLoaderIsRelevant("loader-before", { beforeLoaderId: "loader-before" })).toBe(false);
|
||||||
eventLoaderIsRelevant("loader-before", { beforeLoaderId: "loader-before" }),
|
|
||||||
).toBe(false);
|
|
||||||
expect(
|
expect(
|
||||||
eventLoaderIsRelevant("loader-after", {
|
eventLoaderIsRelevant("loader-after", {
|
||||||
beforeLoaderId: "loader-before",
|
beforeLoaderId: "loader-before",
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ function makeScreenshotDeps(
|
|||||||
opts.get ??
|
opts.get ??
|
||||||
vi.fn(async (tabId: number) => ({ id: tabId, windowId: 100, active: true }) as chrome.tabs.Tab);
|
vi.fn(async (tabId: number) => ({ id: tabId, windowId: 100, active: true }) as chrome.tabs.Tab);
|
||||||
const query =
|
const query =
|
||||||
opts.query ??
|
opts.query ?? vi.fn(async () => [{ id: 7, windowId: 100, active: true } as chrome.tabs.Tab]);
|
||||||
vi.fn(async () => [{ id: 7, windowId: 100, active: true } as chrome.tabs.Tab]);
|
|
||||||
const captureVisibleTab =
|
const captureVisibleTab =
|
||||||
opts.captureVisibleTab ?? vi.fn(async () => `data:image/png;base64,${TINY_PNG}`);
|
opts.captureVisibleTab ?? vi.fn(async () => `data:image/png;base64,${TINY_PNG}`);
|
||||||
const tabsApi = { get, query };
|
const tabsApi = { get, query };
|
||||||
@@ -118,7 +117,11 @@ describe("handleScreenshot", () => {
|
|||||||
const res = await handleScreenshot(
|
const res = await handleScreenshot(
|
||||||
sm,
|
sm,
|
||||||
{ session_id: "aa11" },
|
{ session_id: "aa11" },
|
||||||
makeScreenshotDeps({ captureVisibleTab: vi.fn(), get: emptyGet, query: vi.fn(async () => []) }),
|
makeScreenshotDeps({
|
||||||
|
captureVisibleTab: vi.fn(),
|
||||||
|
get: emptyGet,
|
||||||
|
query: vi.fn(async () => []),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
expect(res).toMatchObject({ code: "not_found" });
|
expect(res).toMatchObject({ code: "not_found" });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -139,9 +139,13 @@ describe("handleSessionStop with auto-return", () => {
|
|||||||
resetAgentOverlays: vi.fn(async () => {}),
|
resetAgentOverlays: vi.fn(async () => {}),
|
||||||
} satisfies AgentOverlayResetApi;
|
} satisfies AgentOverlayResetApi;
|
||||||
|
|
||||||
const res = await handleSessionStop(sm, { session_id: "aa11" }, {
|
const res = await handleSessionStop(
|
||||||
tabManagement: { tabs, windows, agentOverlayReset },
|
sm,
|
||||||
});
|
{ session_id: "aa11" },
|
||||||
|
{
|
||||||
|
tabManagement: { tabs, windows, agentOverlayReset },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if ("code" in res) throw new Error(`unexpected error: ${JSON.stringify(res)}`);
|
if ("code" in res) throw new Error(`unexpected error: ${JSON.stringify(res)}`);
|
||||||
expect(agentOverlayReset.resetAgentOverlays).toHaveBeenCalledWith(7, "aa11");
|
expect(agentOverlayReset.resetAgentOverlays).toHaveBeenCalledWith(7, "aa11");
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ describe("lookupSession", () => {
|
|||||||
expect(lookupSession(sm, { session_id: "" }, "tool.test")).toMatchObject({
|
expect(lookupSession(sm, { session_id: "" }, "tool.test")).toMatchObject({
|
||||||
code: "invalid_params",
|
code: "invalid_params",
|
||||||
});
|
});
|
||||||
expect(
|
expect(lookupSession(sm, { session_id: 42 as unknown as string }, "tool.test")).toMatchObject({
|
||||||
lookupSession(sm, { session_id: 42 as unknown as string }, "tool.test"),
|
|
||||||
).toMatchObject({
|
|
||||||
code: "invalid_params",
|
code: "invalid_params",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -292,7 +292,10 @@ describe("handleTabClose", () => {
|
|||||||
};
|
};
|
||||||
const { api } = makeTabMutationApi(state);
|
const { api } = makeTabMutationApi(state);
|
||||||
const res = await handleTabClose(sm, { session_id: "aa11", tab_id: 9 }, { tabs: api });
|
const res = await handleTabClose(sm, { session_id: "aa11", tab_id: 9 }, { tabs: api });
|
||||||
expect(res).toMatchObject({ code: "permission_denied", data: { reason: "agent_window_scope" } });
|
expect(res).toMatchObject({
|
||||||
|
code: "permission_denied",
|
||||||
|
data: { reason: "agent_window_scope" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects when another session has borrowed the tab", async () => {
|
it("rejects when another session has borrowed the tab", async () => {
|
||||||
@@ -338,7 +341,10 @@ describe("handleTabSelect", () => {
|
|||||||
};
|
};
|
||||||
const { api } = makeTabMutationApi(state);
|
const { api } = makeTabMutationApi(state);
|
||||||
const res = await handleTabSelect(sm, { session_id: "aa11", tab_id: 3 }, { tabs: api });
|
const res = await handleTabSelect(sm, { session_id: "aa11", tab_id: 3 }, { tabs: api });
|
||||||
expect(res).toMatchObject({ code: "permission_denied", data: { reason: "agent_window_scope" } });
|
expect(res).toMatchObject({
|
||||||
|
code: "permission_denied",
|
||||||
|
data: { reason: "agent_window_scope" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects a borrowed tab that is no longer in the Agent Window", async () => {
|
it("rejects a borrowed tab that is no longer in the Agent Window", async () => {
|
||||||
@@ -352,7 +358,10 @@ describe("handleTabSelect", () => {
|
|||||||
};
|
};
|
||||||
const { api } = makeTabMutationApi(state);
|
const { api } = makeTabMutationApi(state);
|
||||||
const res = await handleTabSelect(sm, { session_id: "aa11", tab_id: 3 }, { tabs: api });
|
const res = await handleTabSelect(sm, { session_id: "aa11", tab_id: 3 }, { tabs: api });
|
||||||
expect(res).toMatchObject({ code: "permission_denied", data: { reason: "agent_window_scope" } });
|
expect(res).toMatchObject({
|
||||||
|
code: "permission_denied",
|
||||||
|
data: { reason: "agent_window_scope" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -444,7 +453,10 @@ describe("handleTabBorrow", () => {
|
|||||||
};
|
};
|
||||||
const { api } = makeTabMutationApi(state);
|
const { api } = makeTabMutationApi(state);
|
||||||
const res = await handleTabBorrow(sm, { session_id: "aa11", tab_id: 7 }, { tabs: api });
|
const res = await handleTabBorrow(sm, { session_id: "aa11", tab_id: 7 }, { tabs: api });
|
||||||
expect(res).toMatchObject({ code: "permission_denied", data: { reason: "agent_window_scope" } });
|
expect(res).toMatchObject({
|
||||||
|
code: "permission_denied",
|
||||||
|
data: { reason: "agent_window_scope" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns cancelled when the approver declines", async () => {
|
it("returns cancelled when the approver declines", async () => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
import { ChromiumCdp } from "@/browser-driver/chromium-cdp";
|
import { ChromiumCdp } from "@/browser-driver/chromium-cdp";
|
||||||
import type { SessionManager } from "@/session-manager/manager";
|
import type { SessionManager } from "@/session-manager/manager";
|
||||||
import type { EvaluateError, EvaluateParams, EvaluateResult, RpcError } from "@/transport/types";
|
import type { EvaluateError, EvaluateParams, EvaluateResult, RpcError } from "@/transport/types";
|
||||||
|
import { attachDialogs, markDialogCursor } from "./dialogs";
|
||||||
import {
|
import {
|
||||||
type CdpRunner,
|
type CdpRunner,
|
||||||
type ChromeTabsApi,
|
type ChromeTabsApi,
|
||||||
@@ -27,7 +28,6 @@ import {
|
|||||||
lookupSession,
|
lookupSession,
|
||||||
resolveTargetTab,
|
resolveTargetTab,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { attachDialogs, markDialogCursor } from "./dialogs";
|
|
||||||
|
|
||||||
export interface EvaluateDeps {
|
export interface EvaluateDeps {
|
||||||
cdp: CdpRunner;
|
cdp: CdpRunner;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// until the user clicks Continue / Cancel, the wait times out, or the
|
// until the user clicks Continue / Cancel, the wait times out, or the
|
||||||
// daemon cancels the RPC.
|
// daemon cancels the RPC.
|
||||||
|
|
||||||
import type { SessionManager } from "@/session-manager/manager";
|
|
||||||
import {
|
import {
|
||||||
HELP_CANCEL,
|
HELP_CANCEL,
|
||||||
HELP_REQUEST,
|
HELP_REQUEST,
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
type HelpRequestMessage,
|
type HelpRequestMessage,
|
||||||
type HelpResponseMessage,
|
type HelpResponseMessage,
|
||||||
} from "@/lib/help-bridge";
|
} from "@/lib/help-bridge";
|
||||||
|
import type { SessionManager } from "@/session-manager/manager";
|
||||||
import type {
|
import type {
|
||||||
HelpTarget,
|
HelpTarget,
|
||||||
RequestHelpParams,
|
RequestHelpParams,
|
||||||
@@ -22,7 +22,6 @@ import type {
|
|||||||
ResolvedTarget,
|
ResolvedTarget,
|
||||||
RpcError,
|
RpcError,
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
import { lookupSnapshotRef } from "./snapshot-ref";
|
|
||||||
import {
|
import {
|
||||||
type CdpRunner,
|
type CdpRunner,
|
||||||
type ChromeTabsApi,
|
type ChromeTabsApi,
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
lookupSession,
|
lookupSession,
|
||||||
resolveTargetTab,
|
resolveTargetTab,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
import { lookupSnapshotRef } from "./snapshot-ref";
|
||||||
|
|
||||||
const DEFAULT_HELP_TIMEOUT_MS = 300_000;
|
const DEFAULT_HELP_TIMEOUT_MS = 300_000;
|
||||||
/** Attribute the overlay highlights for resolved `ref` targets. */
|
/** Attribute the overlay highlights for resolved `ref` targets. */
|
||||||
@@ -46,7 +46,9 @@ export type TabNavigationUnsubscribe = () => void;
|
|||||||
|
|
||||||
export interface RequestHelpDeps {
|
export interface RequestHelpDeps {
|
||||||
tabsApi: ChromeTabsApi;
|
tabsApi: ChromeTabsApi;
|
||||||
windows: { update(windowId: number, info: { focused?: boolean }): Promise<chrome.windows.Window> };
|
windows: {
|
||||||
|
update(windowId: number, info: { focused?: boolean }): Promise<chrome.windows.Window>;
|
||||||
|
};
|
||||||
/** Activate a tab inside its window. */
|
/** Activate a tab inside its window. */
|
||||||
activateTab(tabId: number): Promise<void>;
|
activateTab(tabId: number): Promise<void>;
|
||||||
/** Send the help-request to the tab's content script and await reply. */
|
/** Send the help-request to the tab's content script and await reply. */
|
||||||
@@ -68,10 +70,7 @@ export function defaultWatchTabNavigation(
|
|||||||
tabId: number,
|
tabId: number,
|
||||||
onNavigated: () => void,
|
onNavigated: () => void,
|
||||||
): TabNavigationUnsubscribe {
|
): TabNavigationUnsubscribe {
|
||||||
const listener = (
|
const listener = (updatedTabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
|
||||||
updatedTabId: number,
|
|
||||||
changeInfo: chrome.tabs.TabChangeInfo,
|
|
||||||
) => {
|
|
||||||
if (updatedTabId !== tabId) return;
|
if (updatedTabId !== tabId) return;
|
||||||
if (changeInfo.url !== undefined || changeInfo.status === "loading") {
|
if (changeInfo.url !== undefined || changeInfo.status === "loading") {
|
||||||
chrome.tabs.onUpdated.removeListener(listener);
|
chrome.tabs.onUpdated.removeListener(listener);
|
||||||
@@ -102,7 +101,8 @@ function getDefaultDeps(): RequestHelpDeps {
|
|||||||
else resolve(rid ?? id);
|
else resolve(rid ?? id);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
clear: (id) => new Promise((resolve) => chrome.notifications.clear(id, (c) => resolve(c ?? false))),
|
clear: (id) =>
|
||||||
|
new Promise((resolve) => chrome.notifications.clear(id, (c) => resolve(c ?? false))),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,9 @@ async function selectorExists(
|
|||||||
async function clearRefTags(cdp: CdpRunner | undefined, tabId: number): Promise<void> {
|
async function clearRefTags(cdp: CdpRunner | undefined, tabId: number): Promise<void> {
|
||||||
if (!cdp) return;
|
if (!cdp) return;
|
||||||
try {
|
try {
|
||||||
const doc = await cdp.send<{ root?: { nodeId?: number } }>(tabId, "DOM.getDocument", { depth: 0 });
|
const doc = await cdp.send<{ root?: { nodeId?: number } }>(tabId, "DOM.getDocument", {
|
||||||
|
depth: 0,
|
||||||
|
});
|
||||||
const rootId = doc.root?.nodeId;
|
const rootId = doc.root?.nodeId;
|
||||||
if (rootId === undefined) return;
|
if (rootId === undefined) return;
|
||||||
const { nodeIds } = await cdp.send<{ nodeIds: number[] }>(tabId, "DOM.querySelectorAll", {
|
const { nodeIds } = await cdp.send<{ nodeIds: number[] }>(tabId, "DOM.querySelectorAll", {
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ import type {
|
|||||||
MouseButton,
|
MouseButton,
|
||||||
PressParams,
|
PressParams,
|
||||||
PressResult,
|
PressResult,
|
||||||
|
RpcError,
|
||||||
SelectParams,
|
SelectParams,
|
||||||
SelectResult,
|
SelectResult,
|
||||||
RpcError,
|
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
|
import { attachDialogs, markDialogCursor } from "./dialogs";
|
||||||
import {
|
import {
|
||||||
backendNodeToObject,
|
backendNodeToObject,
|
||||||
boxCentre,
|
boxCentre,
|
||||||
@@ -33,7 +34,6 @@ import {
|
|||||||
scrollNodeIntoView,
|
scrollNodeIntoView,
|
||||||
} from "./element-geometry";
|
} from "./element-geometry";
|
||||||
import { rpcError } from "./errors";
|
import { rpcError } from "./errors";
|
||||||
import { resolveSnapshotRef } from "./snapshot-ref";
|
|
||||||
import {
|
import {
|
||||||
type CdpRunner,
|
type CdpRunner,
|
||||||
type ChromeTabsApi,
|
type ChromeTabsApi,
|
||||||
@@ -43,7 +43,7 @@ import {
|
|||||||
lookupSession,
|
lookupSession,
|
||||||
resolveTargetTab,
|
resolveTargetTab,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { attachDialogs, markDialogCursor } from "./dialogs";
|
import { resolveSnapshotRef } from "./snapshot-ref";
|
||||||
|
|
||||||
export interface InteractionDeps {
|
export interface InteractionDeps {
|
||||||
cdp: CdpRunner;
|
cdp: CdpRunner;
|
||||||
@@ -858,7 +858,9 @@ export async function handleSelect(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const attrs = described.node.attributes ?? [];
|
const attrs = described.node.attributes ?? [];
|
||||||
const isMultiple = attrs.some((attr, idx) => idx % 2 === 0 && attr.toLowerCase() === "multiple");
|
const isMultiple = attrs.some(
|
||||||
|
(attr, idx) => idx % 2 === 0 && attr.toLowerCase() === "multiple",
|
||||||
|
);
|
||||||
if (!isMultiple && params.values.length !== 1) {
|
if (!isMultiple && params.values.length !== 1) {
|
||||||
return rpcError(
|
return rpcError(
|
||||||
"invalid_params",
|
"invalid_params",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import type {
|
|||||||
RpcError,
|
RpcError,
|
||||||
WaitUntil,
|
WaitUntil,
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
|
import { attachDialogs, markDialogCursor } from "./dialogs";
|
||||||
import {
|
import {
|
||||||
type CdpRunner,
|
type CdpRunner,
|
||||||
type ChromeTabsApi,
|
type ChromeTabsApi,
|
||||||
@@ -37,7 +38,6 @@ import {
|
|||||||
lookupSession,
|
lookupSession,
|
||||||
resolveTargetTab,
|
resolveTargetTab,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { attachDialogs, markDialogCursor } from "./dialogs";
|
|
||||||
|
|
||||||
export interface NavigationDeps {
|
export interface NavigationDeps {
|
||||||
cdp: CdpRunner;
|
cdp: CdpRunner;
|
||||||
@@ -120,9 +120,7 @@ export function lifecycleAlreadyReached(readyState: string, targetName: string):
|
|||||||
case "DOMContentLoaded":
|
case "DOMContentLoaded":
|
||||||
return readyState === "interactive" || readyState === "complete";
|
return readyState === "interactive" || readyState === "complete";
|
||||||
case "commit":
|
case "commit":
|
||||||
return (
|
return readyState === "interactive" || readyState === "complete" || readyState === "loading";
|
||||||
readyState === "interactive" || readyState === "complete" || readyState === "loading"
|
|
||||||
);
|
|
||||||
case "networkIdle":
|
case "networkIdle":
|
||||||
// Must come from `Page.lifecycleEvent`; readyState does not imply network idle.
|
// Must come from `Page.lifecycleEvent`; readyState does not imply network idle.
|
||||||
return false;
|
return false;
|
||||||
@@ -285,7 +283,13 @@ function startLifecycleWait(
|
|||||||
|
|
||||||
const maybeFinishPending = () => {
|
const maybeFinishPending = () => {
|
||||||
if (settled || !pendingLifecycle) return;
|
if (settled || !pendingLifecycle) return;
|
||||||
if (!acceptLifecycleEvent(pendingLifecycle.name, pendingLifecycle.frameId, pendingLifecycle.loaderId)) {
|
if (
|
||||||
|
!acceptLifecycleEvent(
|
||||||
|
pendingLifecycle.name,
|
||||||
|
pendingLifecycle.frameId,
|
||||||
|
pendingLifecycle.loaderId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (lifecycleMeetsOrExceeds(pendingLifecycle.name, targetName)) {
|
if (lifecycleMeetsOrExceeds(pendingLifecycle.name, targetName)) {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import type {
|
|||||||
SnapshotParams,
|
SnapshotParams,
|
||||||
SnapshotResult,
|
SnapshotResult,
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
|
import { attachDialogs, markDialogCursor } from "./dialogs";
|
||||||
import { nodeBoundingRect, scrollNodeIntoView } from "./element-geometry";
|
import { nodeBoundingRect, scrollNodeIntoView } from "./element-geometry";
|
||||||
import { resolveSnapshotRef } from "./snapshot-ref";
|
|
||||||
import { rpcError } from "./errors";
|
import { rpcError } from "./errors";
|
||||||
import {
|
import {
|
||||||
type ChromeTabsApi,
|
type ChromeTabsApi,
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
type CdpRunner as SharedCdpRunner,
|
type CdpRunner as SharedCdpRunner,
|
||||||
normaliseRef as sharedNormaliseRef,
|
normaliseRef as sharedNormaliseRef,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { attachDialogs, markDialogCursor } from "./dialogs";
|
import { resolveSnapshotRef } from "./snapshot-ref";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Shared helpers (legacy aliases — observation.ts kept exporting these
|
// Shared helpers (legacy aliases — observation.ts kept exporting these
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type {
|
|||||||
WaitForNavigationResult,
|
WaitForNavigationResult,
|
||||||
WaitUntil,
|
WaitUntil,
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
|
import { attachDialogs, markDialogCursor } from "./dialogs";
|
||||||
import { cdpLifecycleName, ensureCdpReady, waitForLifecyclePassive } from "./navigation";
|
import { cdpLifecycleName, ensureCdpReady, waitForLifecyclePassive } from "./navigation";
|
||||||
import {
|
import {
|
||||||
type CdpRunner,
|
type CdpRunner,
|
||||||
@@ -30,7 +31,6 @@ import {
|
|||||||
lookupSession,
|
lookupSession,
|
||||||
resolveTargetTab,
|
resolveTargetTab,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { attachDialogs, markDialogCursor } from "./dialogs";
|
|
||||||
|
|
||||||
export interface WaitForNavigationDeps {
|
export interface WaitForNavigationDeps {
|
||||||
cdp: CdpRunner;
|
cdp: CdpRunner;
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
--input: oklch(0.35 0.02 60);
|
--input: oklch(0.35 0.02 60);
|
||||||
--ring: oklch(0.7 0.18 45);
|
--ring: oklch(0.7 0.18 45);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
@@ -68,7 +67,6 @@
|
|||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
@@ -84,7 +82,6 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,10 +85,7 @@ function assetEntry(platformKey, triple) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const assets = Object.fromEntries(
|
const assets = Object.fromEntries(
|
||||||
Object.entries(PLATFORMS).map(([key, triple]) => [
|
Object.entries(PLATFORMS).map(([key, triple]) => [key, assetEntry(key, triple)]),
|
||||||
key,
|
|
||||||
assetEntry(key, triple),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import { execFileSync } from "node:child_process";
|
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
const scriptPath = join(scriptDir, "render-version-json.mjs");
|
const scriptPath = join(scriptDir, "render-version-json.mjs");
|
||||||
@@ -20,14 +20,8 @@ test("renders asset URLs with sha256 checksums from dist files", () => {
|
|||||||
mkdirSync(dist);
|
mkdirSync(dist);
|
||||||
writeFileSync(join(dist, archive), archiveBytes);
|
writeFileSync(join(dist, archive), archiveBytes);
|
||||||
writeFileSync(join(dist, `bsk-v${version}-x86_64-apple-darwin.tar.gz`), "darwin x64\n");
|
writeFileSync(join(dist, `bsk-v${version}-x86_64-apple-darwin.tar.gz`), "darwin x64\n");
|
||||||
writeFileSync(
|
writeFileSync(join(dist, `bsk-v${version}-x86_64-unknown-linux-musl.tar.gz`), "linux x64\n");
|
||||||
join(dist, `bsk-v${version}-x86_64-unknown-linux-musl.tar.gz`),
|
writeFileSync(join(dist, `bsk-v${version}-aarch64-unknown-linux-musl.tar.gz`), "linux arm64\n");
|
||||||
"linux x64\n",
|
|
||||||
);
|
|
||||||
writeFileSync(
|
|
||||||
join(dist, `bsk-v${version}-aarch64-unknown-linux-musl.tar.gz`),
|
|
||||||
"linux arm64\n",
|
|
||||||
);
|
|
||||||
writeFileSync(join(dist, `bsk-v${version}-x86_64-pc-windows-msvc.zip`), "windows x64\n");
|
writeFileSync(join(dist, `bsk-v${version}-x86_64-pc-windows-msvc.zip`), "windows x64\n");
|
||||||
|
|
||||||
execFileSync(process.execPath, [
|
execFileSync(process.execPath, [
|
||||||
|
|||||||
Reference in New Issue
Block a user