如何用Python的Selenium读取Safari Web Extension的本地存储?
Firefox WebDriver的实现方式
我可以通过以下代码读取Firefox扩展的本地存储:
extension_path = "/path/to/my/extension" info = { "extension_id": "foobar", "uuid": uuid.uuid4(), } base_url = f"moz-extension://{info['uuid']}/" opts = FirefoxOptions() opts.set_preference('extensions.webextensions.uuids', '{"%s": "%s"}' % ( info["extension_id"], info["uuid"])) driver = webdriver.Firefox(options=opts) driver.install_addon(extension_path, temporary=True) driver.get(f"{base_url}_generated_background_page.html") results = driver.execute_async_script(( "let done = arguments[arguments.length - 1]," " store_name = arguments[0];" "browser.storage.local.get([store_name], function (res) {" " done(res[store_name]);" "});" ), "foo")
Safari WebDriver遇到的问题
我已经通过xcrun safari-web-extension-converter /path/to/my/extension完成扩展移植,构建并手动测试确认扩展可正常运行。通过Develop -> Web Extension Background Pages -> <my web extension>可以找到扩展ID,后台页面地址为safari-web-extension://<id>/_generated_background_page.html。
但运行以下代码时,Selenium会在driver.get(f"{base_url}_generated_background_page.html")处冻结:
base_url = f"safari-web-extension://<id>/" driver = webdriver.Safari() driver.get(f"{base_url}_generated_background_page.html") results = driver.execute_async_script(( "let done = arguments[arguments.length - 1]," " store_name = arguments[0];" "browser.storage.local.get([store_name], function (res) {" " done(res[store_name]);" "});" ), "foo")
2023年2月8日更新:尝试runtime.sendMessage的问题
我尝试用browser.runtime.sendMessage的方式实现,但读取到的是手动打开的Safari实例中的扩展存储,而非Selenium启动的实例存储:
Selenium代码
results = driver.execute_async_script(( "let done = arguments[arguments.length - 1]," " store_name = arguments[0];" " browser.runtime.sendMessage('com.oskar.foo.Extension (Apple Team ID)', {}, function (res) {" " done(res[store_name]);" " });" ), "foo")
扩展background.js代码
browser.runtime.onMessageExternal.addListener(function ( request, sender, sendResponse ) { browser.storage.local.get("foo").then((j) => { sendResponse(j); }); return true; });
manifest.json配置
"externally_connectable": { "ids": ["*"], "matches": ["https://example.org/*"] }
解决方法
核心思路
Safari WebDriver直接访问扩展后台页面会触发沙箱限制导致冻结,且ids: ["*"]的配置会让消息被所有扩展实例接收。需要通过普通页面注入脚本+精准消息通信的方式,确保访问的是Selenium启动实例的扩展存储。
具体步骤
修改扩展的externally_connectable配置
仅允许Selenium控制的页面(如空白页、本地测试页)通信,避免跨实例干扰:"externally_connectable": { "matches": ["about:blank", "http://localhost:*/*"] }更新扩展background.js的消息监听逻辑
增加操作标识,明确响应存储读取请求:browser.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { // 仅处理指定的存储读取操作 if (request.action === "fetchStorage") { browser.storage.local.get(request.key).then(result => { sendResponse({value: result[request.key]}); }); return true; // 保持端口开放,支持异步返回结果 } });调整Selenium代码,通过普通页面通信
不要直接访问扩展后台页面,而是打开空白页后注入脚本发送消息:from selenium import webdriver # 替换为你的扩展ID(可从Xcode项目Info.plist或Safari Develop菜单获取) EXTENSION_ID = "com.yourteam.yourextension" driver = webdriver.Safari() # 打开空白页作为通信载体 driver.get("about:blank") # 执行异步脚本发送消息并获取存储值 results = driver.execute_async_script(""" const done = arguments[arguments.length - 1]; const targetKey = arguments[0]; browser.runtime.sendMessage( "%s", {action: "fetchStorage", key: targetKey}, (response) => { done(response.value); } ); """ % EXTENSION_ID, "foo") print(results) driver.quit()
关键注意事项
- Selenium启动的Safari是独立的沙箱实例,需确保该实例中已启用目标扩展:打开Selenium启动的Safari后,通过
Develop -> Allow Unsigned Extensions,再在Safari设置 -> 扩展中开启你的扩展。 - 不要使用
ids: ["*"],否则消息可能被其他Safari实例的扩展接收,导致读取到错误的存储数据。 - 禁止直接访问扩展内部页面(如
_generated_background_page.html),Safari WebDriver对这类页面的访问有严格限制,会导致会话冻结。
内容的提问来源于stack exchange,提问作者Oskar Persson

