惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
Google DeepMind News
Google DeepMind News
V
V2EX
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
Recent Announcements
Recent Announcements
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tailwind CSS Blog
美团技术团队
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(google-meet): grant browser media permissions · openc...
steipete · 2026-04-27 · via Recent Commits to openclaw:main

@@ -0,0 +1,133 @@

1+

import { beforeEach, describe, expect, it, vi } from "vitest";

2+

import { createBrowserRouteApp, createBrowserRouteResponse } from "./test-helpers.js";

3+4+

const cdpMocks = vi.hoisted(() => ({

5+

getChromeWebSocketUrl: vi.fn(async () => "ws://127.0.0.1:18800/devtools/browser/test"),

6+

send: vi.fn(

7+

async (

8+

_method: string,

9+

_params?: Record<string, unknown>,

10+

): Promise<Record<string, unknown>> => ({}),

11+

),

12+

withCdpSocket: vi.fn(

13+

async (

14+

_wsUrl: string,

15+

fn: (

16+

send: (method: string, params?: Record<string, unknown>) => Promise<unknown>,

17+

) => Promise<unknown>,

18+

) => await fn(cdpMocks.send),

19+

),

20+

}));

21+22+

vi.mock("../chrome.js", () => ({

23+

getChromeWebSocketUrl: cdpMocks.getChromeWebSocketUrl,

24+

}));

25+26+

vi.mock("../cdp.helpers.js", () => ({

27+

withCdpSocket: cdpMocks.withCdpSocket,

28+

}));

29+30+

const { registerBrowserPermissionRoutes } = await import("./permissions.js");

31+32+

function createProfileContext() {

33+

return {

34+

profile: {

35+

name: "openclaw",

36+

cdpUrl: "http://127.0.0.1:18800",

37+

},

38+

ensureBrowserAvailable: vi.fn(async () => {}),

39+

ensureTabAvailable: vi.fn(),

40+

isHttpReachable: vi.fn(),

41+

isTransportAvailable: vi.fn(),

42+

isReachable: vi.fn(),

43+

listTabs: vi.fn(),

44+

openTab: vi.fn(),

45+

labelTab: vi.fn(),

46+

focusTab: vi.fn(),

47+

closeTab: vi.fn(),

48+

stopRunningBrowser: vi.fn(),

49+

resetProfile: vi.fn(),

50+

};

51+

}

52+53+

function createRouteContext(profileCtx: ReturnType<typeof createProfileContext>) {

54+

return {

55+

state: () => ({ resolved: { ssrfPolicy: { allowPrivateNetwork: false } } }),

56+

forProfile: () => profileCtx,

57+

listProfiles: vi.fn(async () => []),

58+

mapTabError: vi.fn(() => null),

59+

...profileCtx,

60+

};

61+

}

62+63+

async function callGrant(body: Record<string, unknown>) {

64+

const { app, postHandlers } = createBrowserRouteApp();

65+

const profileCtx = createProfileContext();

66+

registerBrowserPermissionRoutes(app, createRouteContext(profileCtx) as never);

67+

const handler = postHandlers.get("/permissions/grant");

68+

expect(handler).toBeTypeOf("function");

69+70+

const response = createBrowserRouteResponse();

71+

await handler?.({ params: {}, query: {}, body }, response.res);

72+

return { response, profileCtx };

73+

}

74+75+

describe("browser permission routes", () => {

76+

beforeEach(() => {

77+

cdpMocks.getChromeWebSocketUrl.mockClear();

78+

cdpMocks.send.mockReset().mockResolvedValue({});

79+

cdpMocks.withCdpSocket.mockClear();

80+

});

81+82+

it("grants required and optional Chrome permissions for an origin", async () => {

83+

const { response, profileCtx } = await callGrant({

84+

origin: "https://meet.google.com/abc-defg-hij",

85+

permissions: ["audioCapture", "videoCapture"],

86+

optionalPermissions: ["speakerSelection"],

87+

timeoutMs: 1234,

88+

});

89+90+

expect(response.statusCode).toBe(200);

91+

expect(response.body).toMatchObject({

92+

ok: true,

93+

origin: "https://meet.google.com",

94+

grantedPermissions: ["audioCapture", "videoCapture", "speakerSelection"],

95+

unsupportedPermissions: [],

96+

});

97+

expect(profileCtx.ensureBrowserAvailable).toHaveBeenCalled();

98+

expect(cdpMocks.getChromeWebSocketUrl).toHaveBeenCalledWith("http://127.0.0.1:18800", 1234, {

99+

allowPrivateNetwork: false,

100+

});

101+

expect(cdpMocks.send).toHaveBeenCalledWith("Browser.grantPermissions", {

102+

origin: "https://meet.google.com",

103+

permissions: ["audioCapture", "videoCapture", "speakerSelection"],

104+

});

105+

});

106+107+

it("keeps required permissions when an optional permission is unsupported", async () => {

108+

cdpMocks.send.mockImplementation(async (_method: string, params?: Record<string, unknown>) => {

109+

const permissions = Array.isArray(params?.permissions) ? params.permissions : [];

110+

if (permissions.includes("speakerSelection")) {

111+

throw new Error("Unknown permission type");

112+

}

113+

return {};

114+

});

115+116+

const { response } = await callGrant({

117+

origin: "https://meet.google.com",

118+

permissions: ["audioCapture", "videoCapture"],

119+

optionalPermissions: ["speakerSelection"],

120+

});

121+122+

expect(response.statusCode).toBe(200);

123+

expect(response.body).toMatchObject({

124+

ok: true,

125+

grantedPermissions: ["audioCapture", "videoCapture"],

126+

unsupportedPermissions: ["speakerSelection"],

127+

});

128+

expect(cdpMocks.send).toHaveBeenNthCalledWith(2, "Browser.grantPermissions", {

129+

origin: "https://meet.google.com",

130+

permissions: ["audioCapture", "videoCapture"],

131+

});

132+

});

133+

});