mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
refactor(google-meet): tidy browser create control
This commit is contained in:
@@ -74,6 +74,39 @@ function captureStdout() {
|
||||
};
|
||||
}
|
||||
|
||||
async function runCreateMeetBrowserScript(params: { buttonText: string }) {
|
||||
const location = {
|
||||
href: "https://meet.google.com/new",
|
||||
hostname: "meet.google.com",
|
||||
};
|
||||
const button = {
|
||||
disabled: false,
|
||||
innerText: params.buttonText,
|
||||
textContent: params.buttonText,
|
||||
getAttribute: (name: string) => (name === "aria-label" ? params.buttonText : null),
|
||||
click: vi.fn(() => {
|
||||
location.href = "https://meet.google.com/abc-defg-hij";
|
||||
}),
|
||||
};
|
||||
const document = {
|
||||
title: "Meet",
|
||||
body: {
|
||||
innerText: "Do you want people to hear you in the meeting?",
|
||||
textContent: "Do you want people to hear you in the meeting?",
|
||||
},
|
||||
querySelectorAll: (selector: string) => (selector === "button" ? [button] : []),
|
||||
};
|
||||
vi.stubGlobal("document", document);
|
||||
vi.stubGlobal("location", location);
|
||||
const fn = (0, eval)(`(${CREATE_MEET_FROM_BROWSER_SCRIPT})`) as () => Promise<{
|
||||
meetingUri?: string;
|
||||
manualActionReason?: string;
|
||||
notes?: string[];
|
||||
retryAfterMs?: number;
|
||||
}>;
|
||||
return { button, result: await fn() };
|
||||
}
|
||||
|
||||
type TestBridgeProcess = {
|
||||
stdin?: { write(chunk: unknown): unknown } | null;
|
||||
stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null;
|
||||
@@ -853,50 +886,15 @@ describe("google-meet plugin", () => {
|
||||
])(
|
||||
"uses browser automation for Meet's %s choice during browser creation",
|
||||
async (buttonText, note) => {
|
||||
const location = {
|
||||
href: "https://meet.google.com/new",
|
||||
hostname: "meet.google.com",
|
||||
};
|
||||
const button = {
|
||||
disabled: false,
|
||||
innerText: buttonText,
|
||||
textContent: buttonText,
|
||||
getAttribute: (name: string) => (name === "aria-label" ? buttonText : null),
|
||||
click: vi.fn(() => {
|
||||
location.href = "https://meet.google.com/abc-defg-hij";
|
||||
}),
|
||||
};
|
||||
const document = {
|
||||
title: "Meet",
|
||||
body: {
|
||||
innerText: "Do you want people to hear you in the meeting?",
|
||||
textContent: "Do you want people to hear you in the meeting?",
|
||||
},
|
||||
querySelectorAll: (selector: string) => (selector === "button" ? [button] : []),
|
||||
};
|
||||
vi.stubGlobal("document", document);
|
||||
vi.stubGlobal("location", location);
|
||||
vi.useFakeTimers();
|
||||
const { button, result } = await runCreateMeetBrowserScript({ buttonText });
|
||||
|
||||
try {
|
||||
const fn = (0, eval)(`(${CREATE_MEET_FROM_BROWSER_SCRIPT})`) as () => Promise<{
|
||||
meetingUri?: string;
|
||||
manualActionReason?: string;
|
||||
notes?: string[];
|
||||
retryAfterMs?: number;
|
||||
}>;
|
||||
const result = await fn();
|
||||
|
||||
expect(result).toMatchObject({
|
||||
retryAfterMs: 1000,
|
||||
notes: [note],
|
||||
});
|
||||
expect(button.click).toHaveBeenCalledTimes(1);
|
||||
expect(result.meetingUri).toBeUndefined();
|
||||
expect(result.manualActionReason).toBeUndefined();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
expect(result).toMatchObject({
|
||||
retryAfterMs: 1000,
|
||||
notes: [note],
|
||||
});
|
||||
expect(button.click).toHaveBeenCalledTimes(1);
|
||||
expect(result.meetingUri).toBeUndefined();
|
||||
expect(result.manualActionReason).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -231,6 +231,20 @@ type BrowserTab = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
type BrowserCreateStepResult = {
|
||||
meetingUri?: string;
|
||||
browserUrl?: string;
|
||||
browserTitle?: string;
|
||||
manualAction?: string;
|
||||
manualActionReason?: GoogleMeetChromeHealth["manualActionReason"];
|
||||
notes?: string[];
|
||||
retryAfterMs?: number;
|
||||
};
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function formatBrowserAutomationError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
@@ -242,6 +256,12 @@ function formatBrowserAutomationError(error: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
function isBrowserNavigationInterruption(error: unknown): boolean {
|
||||
return /execution context was destroyed|navigation|target closed/i.test(
|
||||
formatBrowserAutomationError(error),
|
||||
);
|
||||
}
|
||||
|
||||
export type GoogleMeetBrowserCreateResult = {
|
||||
meetingUri: string;
|
||||
nodeId: string;
|
||||
@@ -304,15 +324,13 @@ function readBrowserTab(result: unknown): BrowserTab | undefined {
|
||||
return result && typeof result === "object" ? (result as BrowserTab) : undefined;
|
||||
}
|
||||
|
||||
function readBrowserCreateResult(result: unknown): {
|
||||
meetingUri?: string;
|
||||
browserUrl?: string;
|
||||
browserTitle?: string;
|
||||
manualAction?: string;
|
||||
manualActionReason?: GoogleMeetChromeHealth["manualActionReason"];
|
||||
notes?: string[];
|
||||
retryAfterMs?: number;
|
||||
} {
|
||||
function readStringArray(value: unknown): string[] | undefined {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string")
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readBrowserCreateResult(result: unknown): BrowserCreateStepResult {
|
||||
const record = result && typeof result === "object" ? (result as Record<string, unknown>) : {};
|
||||
const nested =
|
||||
record.result && typeof record.result === "object"
|
||||
@@ -327,9 +345,7 @@ function readBrowserCreateResult(result: unknown): {
|
||||
typeof nested.manualActionReason === "string"
|
||||
? (nested.manualActionReason as GoogleMeetChromeHealth["manualActionReason"])
|
||||
: undefined,
|
||||
notes: Array.isArray(nested.notes)
|
||||
? nested.notes.filter((note): note is string => typeof note === "string")
|
||||
: undefined,
|
||||
notes: readStringArray(nested.notes),
|
||||
retryAfterMs:
|
||||
typeof nested.retryAfterMs === "number" && Number.isFinite(nested.retryAfterMs)
|
||||
? nested.retryAfterMs
|
||||
@@ -454,7 +470,7 @@ export async function createMeetWithBrowserProxyOnNode(params: {
|
||||
throw new Error("Browser fallback opened Google Meet but did not return a targetId.");
|
||||
}
|
||||
const notes = new Set<string>();
|
||||
let lastResult: ReturnType<typeof readBrowserCreateResult> | undefined;
|
||||
let lastResult: BrowserCreateStepResult | undefined;
|
||||
let lastError: unknown;
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() <= deadline) {
|
||||
@@ -493,13 +509,13 @@ export async function createMeetWithBrowserProxyOnNode(params: {
|
||||
}
|
||||
throw new Error(result.manualAction);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, result.retryAfterMs ?? 500));
|
||||
await sleep(result.retryAfterMs ?? 500);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (!/execution context was destroyed|navigation|target closed/i.test(String(error))) {
|
||||
if (!isBrowserNavigationInterruption(error)) {
|
||||
throw error;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
await sleep(1_000);
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user