From 8f7f2a6e8e85eb2fdb03c2493b9f023eb5778d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Wed, 27 May 2026 01:27:23 +0800 Subject: [PATCH] feat(hermes): add browser advanced controls --- scripts/dev-api.js | 20 ++++ src-tauri/src/commands/hermes.rs | 177 +++++++++++++++++++++++++++- src/engines/hermes/pages/config.js | 33 ++++++ src/locales/modules/engine.js | 11 +- tests/hermes-browser-config.test.js | 52 +++++++- tests/hermes-config-page-ui.test.js | 5 + 6 files changed, 294 insertions(+), 4 deletions(-) diff --git a/scripts/dev-api.js b/scripts/dev-api.js index f68384e..be1332c 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -3325,6 +3325,7 @@ const HERMES_STREAMING_TRANSPORTS = new Set(['auto', 'draft', 'edit', 'off']) const HERMES_CODE_EXECUTION_MODES = new Set(['project', 'strict']) const HERMES_TERMINAL_BACKENDS = new Set(['local', 'ssh', 'docker', 'singularity', 'modal', 'daytona', 'vercel_sandbox']) const HERMES_BROWSER_ENGINES = new Set(['auto', 'lightpanda', 'chrome']) +const HERMES_BROWSER_DIALOG_POLICIES = new Set(['must_respond', 'auto_dismiss', 'auto_accept']) const HERMES_STT_PROVIDERS = new Set(['auto', 'local', 'groq', 'openai', 'mistral']) const HERMES_STT_LOCAL_MODELS = new Set(['tiny', 'base', 'small', 'medium', 'large-v3', 'turbo']) const HERMES_STT_OPENAI_MODELS = new Set(['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe']) @@ -3462,6 +3463,13 @@ function normalizeHermesBrowserEngine(value, strict = false) { return 'auto' } +function normalizeHermesBrowserDialogPolicy(value, strict = false) { + const policy = String(value ?? '').trim().toLowerCase() || 'must_respond' + if (HERMES_BROWSER_DIALOG_POLICIES.has(policy)) return policy + if (strict) throw new Error('browser.dialog_policy 必须是 must_respond、auto_dismiss 或 auto_accept') + return 'must_respond' +} + function normalizeHermesSttProvider(value, strict = false) { const provider = String(value ?? '').trim().toLowerCase() || 'auto' if (HERMES_STT_PROVIDERS.has(provider)) return provider @@ -5293,6 +5301,11 @@ export function buildHermesBrowserConfigValues(config = {}) { browserCommandTimeout: parseHermesInteger(browser.command_timeout, 'browser.command_timeout', 30, 5, 3600, false), browserRecordSessions: readHermesBool(browser.record_sessions, false), browserEngine: normalizeHermesBrowserEngine(browser.engine, false), + browserAllowPrivateUrls: readHermesBool(browser.allow_private_urls, false), + browserAutoLocalForPrivateUrls: readHermesBool(browser.auto_local_for_private_urls, true), + browserCdpUrl: normalizeHermesOptionalString(browser.cdp_url, 'browser.cdp_url'), + browserDialogPolicy: normalizeHermesBrowserDialogPolicy(browser.dialog_policy, false), + browserDialogTimeout: parseHermesInteger(browser.dialog_timeout_s, 'browser.dialog_timeout_s', 300, 1, 86400, false), } } @@ -5306,6 +5319,13 @@ export function mergeHermesBrowserConfig(config = {}, form = {}) { browser.command_timeout = parseHermesInteger(Object.hasOwn(form, 'browserCommandTimeout') ? form.browserCommandTimeout : currentValues.browserCommandTimeout, 'browser.command_timeout', 30, 5, 3600, true) browser.record_sessions = formHermesBool(form, 'browserRecordSessions', currentValues.browserRecordSessions) browser.engine = normalizeHermesBrowserEngine(Object.hasOwn(form, 'browserEngine') ? form.browserEngine : currentValues.browserEngine, true) + browser.allow_private_urls = formHermesBool(form, 'browserAllowPrivateUrls', currentValues.browserAllowPrivateUrls) + browser.auto_local_for_private_urls = formHermesBool(form, 'browserAutoLocalForPrivateUrls', currentValues.browserAutoLocalForPrivateUrls) + const cdpUrl = normalizeHermesOptionalString(Object.hasOwn(form, 'browserCdpUrl') ? form.browserCdpUrl : currentValues.browserCdpUrl, 'browser.cdp_url') + if (cdpUrl) browser.cdp_url = cdpUrl + else delete browser.cdp_url + browser.dialog_policy = normalizeHermesBrowserDialogPolicy(Object.hasOwn(form, 'browserDialogPolicy') ? form.browserDialogPolicy : currentValues.browserDialogPolicy, true) + browser.dialog_timeout_s = parseHermesInteger(Object.hasOwn(form, 'browserDialogTimeout') ? form.browserDialogTimeout : currentValues.browserDialogTimeout, 'browser.dialog_timeout_s', 300, 1, 86400, true) next.browser = browser return next } diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index fc5ec0a..5bdd107 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -6892,6 +6892,29 @@ fn normalize_hermes_browser_engine(value: Option, strict: bool) -> Resul } } +fn normalize_hermes_browser_dialog_policy( + value: Option, + strict: bool, +) -> Result { + let policy = value.unwrap_or_default().trim().to_ascii_lowercase(); + let policy = if policy.is_empty() { + "must_respond".to_string() + } else { + policy + }; + if matches!( + policy.as_str(), + "must_respond" | "auto_dismiss" | "auto_accept" + ) { + return Ok(policy); + } + if strict { + Err("browser.dialog_policy 必须是 must_respond、auto_dismiss 或 auto_accept".to_string()) + } else { + Ok("must_respond".to_string()) + } +} + fn normalize_hermes_stt_provider(value: Option, strict: bool) -> Result { let provider = value.unwrap_or_default().trim().to_ascii_lowercase(); let provider = if provider.is_empty() { @@ -7785,12 +7808,34 @@ fn build_hermes_browser_config_values(config: &serde_yaml::Value) -> Value { false, ) .unwrap_or_else(|_| "auto".to_string()); + let browser_allow_private_urls = browser + .and_then(|map| yaml_bool_field(map, "allow_private_urls")) + .unwrap_or(false); + let browser_auto_local_for_private_urls = browser + .and_then(|map| yaml_bool_field(map, "auto_local_for_private_urls")) + .unwrap_or(true); + let browser_cdp_url = browser + .and_then(|map| yaml_string_field(map, "cdp_url")) + .unwrap_or_default(); + let browser_dialog_policy = normalize_hermes_browser_dialog_policy( + browser.and_then(|map| yaml_string_field(map, "dialog_policy")), + false, + ) + .unwrap_or_else(|_| "must_respond".to_string()); + let browser_dialog_timeout = browser + .map(|map| bounded_hermes_i64(yaml_i64_field(map, "dialog_timeout_s"), 300, 1, 86400)) + .unwrap_or(300); serde_json::json!({ "browserInactivityTimeout": browser_inactivity_timeout, "browserCommandTimeout": browser_command_timeout, "browserRecordSessions": browser_record_sessions, "browserEngine": browser_engine, + "browserAllowPrivateUrls": browser_allow_private_urls, + "browserAutoLocalForPrivateUrls": browser_auto_local_for_private_urls, + "browserCdpUrl": browser_cdp_url, + "browserDialogPolicy": browser_dialog_policy, + "browserDialogTimeout": browser_dialog_timeout, }) } @@ -7828,6 +7873,51 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) -> }, true, )?; + let browser_allow_private_urls = + form_bool(form, "browserAllowPrivateUrls").unwrap_or_else(|| { + current["browserAllowPrivateUrls"] + .as_bool() + .unwrap_or(false) + }); + let browser_auto_local_for_private_urls = form_bool(form, "browserAutoLocalForPrivateUrls") + .unwrap_or_else(|| { + current["browserAutoLocalForPrivateUrls"] + .as_bool() + .unwrap_or(true) + }); + let browser_cdp_url = if form.get("browserCdpUrl").is_some() { + form_string(form, "browserCdpUrl") + .ok_or_else(|| "browser.cdp_url 必须是字符串".to_string())? + .trim() + .to_string() + } else { + current["browserCdpUrl"] + .as_str() + .unwrap_or_default() + .trim() + .to_string() + }; + let browser_dialog_policy = normalize_hermes_browser_dialog_policy( + if form.get("browserDialogPolicy").is_some() { + form_string(form, "browserDialogPolicy") + } else { + current["browserDialogPolicy"] + .as_str() + .map(ToString::to_string) + }, + true, + )?; + let browser_dialog_timeout = validate_hermes_i64( + if form.get("browserDialogTimeout").is_some() { + form_i64(form, "browserDialogTimeout") + } else { + Some(current["browserDialogTimeout"].as_i64().unwrap_or(300)) + }, + "browser.dialog_timeout_s", + 300, + 1, + 86400, + )?; let root = ensure_yaml_object(config)?; let browser = yaml_child_object(root, "browser")?; @@ -7847,6 +7937,23 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) -> yaml_key("engine"), serde_yaml::Value::String(browser_engine), ); + browser.insert( + yaml_key("allow_private_urls"), + serde_yaml::Value::Bool(browser_allow_private_urls), + ); + browser.insert( + yaml_key("auto_local_for_private_urls"), + serde_yaml::Value::Bool(browser_auto_local_for_private_urls), + ); + set_optional_yaml_string(browser, "cdp_url", browser_cdp_url); + browser.insert( + yaml_key("dialog_policy"), + serde_yaml::Value::String(browser_dialog_policy), + ); + browser.insert( + yaml_key("dialog_timeout_s"), + serde_yaml::Value::Number(browser_dialog_timeout.into()), + ); Ok(()) } @@ -16370,6 +16477,11 @@ mod hermes_browser_config_tests { assert_eq!(values["browserCommandTimeout"], 30); assert_eq!(values["browserRecordSessions"], false); assert_eq!(values["browserEngine"], "auto"); + assert_eq!(values["browserAllowPrivateUrls"], false); + assert_eq!(values["browserAutoLocalForPrivateUrls"], true); + assert_eq!(values["browserCdpUrl"], ""); + assert_eq!(values["browserDialogPolicy"], "must_respond"); + assert_eq!(values["browserDialogTimeout"], 300); } #[test] @@ -16381,6 +16493,11 @@ browser: command_timeout: 45 record_sessions: true engine: lightpanda + allow_private_urls: true + auto_local_for_private_urls: false + cdp_url: ws://127.0.0.1:9222/devtools/browser/demo + dialog_policy: auto_accept + dialog_timeout_s: 120 "#, ) .unwrap(); @@ -16389,6 +16506,14 @@ browser: assert_eq!(values["browserCommandTimeout"], 45); assert_eq!(values["browserRecordSessions"], true); assert_eq!(values["browserEngine"], "lightpanda"); + assert_eq!(values["browserAllowPrivateUrls"], true); + assert_eq!(values["browserAutoLocalForPrivateUrls"], false); + assert_eq!( + values["browserCdpUrl"], + "ws://127.0.0.1:9222/devtools/browser/demo" + ); + assert_eq!(values["browserDialogPolicy"], "auto_accept"); + assert_eq!(values["browserDialogTimeout"], 120); } #[test] @@ -16419,6 +16544,11 @@ streaming: "browserCommandTimeout": "60", "browserRecordSessions": true, "browserEngine": "chrome", + "browserAllowPrivateUrls": true, + "browserAutoLocalForPrivateUrls": false, + "browserCdpUrl": "http://127.0.0.1:9222", + "browserDialogPolicy": "auto_dismiss", + "browserDialogTimeout": "45", }), ) .unwrap(); @@ -16430,9 +16560,22 @@ streaming: assert_eq!(config["browser"]["record_sessions"].as_bool(), Some(true)); assert_eq!(config["browser"]["engine"].as_str(), Some("chrome")); assert_eq!( - config["browser"]["cdp_url"].as_str(), - Some("ws://127.0.0.1:9222/devtools/browser/demo") + config["browser"]["allow_private_urls"].as_bool(), + Some(true) ); + assert_eq!( + config["browser"]["auto_local_for_private_urls"].as_bool(), + Some(false) + ); + assert_eq!( + config["browser"]["cdp_url"].as_str(), + Some("http://127.0.0.1:9222") + ); + assert_eq!( + config["browser"]["dialog_policy"].as_str(), + Some("auto_dismiss") + ); + assert_eq!(config["browser"]["dialog_timeout_s"].as_i64(), Some(45)); assert_eq!( config["browser"]["camofox"]["managed_persistence"].as_bool(), Some(true) @@ -16443,6 +16586,26 @@ streaming: ); } + #[test] + fn merge_browser_config_removes_empty_cdp_url() { + let mut config: serde_yaml::Value = serde_yaml::from_str( + r#" +browser: + cdp_url: ws://127.0.0.1:9222/devtools/browser/demo + custom_flag: keep-browser +"#, + ) + .unwrap(); + + merge_hermes_browser_config(&mut config, &json!({ "browserCdpUrl": " " })).unwrap(); + + assert_eq!( + config["browser"]["custom_flag"].as_str(), + Some("keep-browser") + ); + assert!(config["browser"]["cdp_url"].is_null()); + } + #[test] fn merge_browser_config_rejects_invalid_values() { let mut config = serde_yaml::Value::Mapping(serde_yaml::Mapping::new()); @@ -16456,6 +16619,16 @@ streaming: let err = merge_hermes_browser_config(&mut config, &json!({ "browserCommandTimeout": 4 })) .unwrap_err(); assert!(err.contains("browser.command_timeout")); + let err = + merge_hermes_browser_config(&mut config, &json!({ "browserDialogPolicy": "ignore" })) + .unwrap_err(); + assert!(err.contains("browser.dialog_policy")); + let err = merge_hermes_browser_config(&mut config, &json!({ "browserDialogTimeout": 0 })) + .unwrap_err(); + assert!(err.contains("browser.dialog_timeout_s")); + let err = + merge_hermes_browser_config(&mut config, &json!({ "browserCdpUrl": 123 })).unwrap_err(); + assert!(err.contains("browser.cdp_url")); } } diff --git a/src/engines/hermes/pages/config.js b/src/engines/hermes/pages/config.js index ede400e..fc27122 100644 --- a/src/engines/hermes/pages/config.js +++ b/src/engines/hermes/pages/config.js @@ -260,6 +260,11 @@ const BROWSER_DEFAULTS = { browserCommandTimeout: 30, browserRecordSessions: false, browserEngine: 'auto', + browserAllowPrivateUrls: false, + browserAutoLocalForPrivateUrls: true, + browserCdpUrl: '', + browserDialogPolicy: 'must_respond', + browserDialogTimeout: 300, } const STT_DEFAULTS = { @@ -298,6 +303,7 @@ const STREAMING_TRANSPORTS = ['edit', 'auto', 'draft', 'off'] const CODE_EXECUTION_MODES = ['project', 'strict'] const TERMINAL_BACKENDS = ['local', 'ssh', 'docker', 'singularity', 'modal', 'daytona', 'vercel_sandbox'] const BROWSER_ENGINES = ['auto', 'lightpanda', 'chrome'] +const BROWSER_DIALOG_POLICIES = ['must_respond', 'auto_dismiss', 'auto_accept'] const STT_PROVIDERS = ['auto', 'local', 'groq', 'openai', 'mistral'] const STT_LOCAL_MODELS = ['tiny', 'base', 'small', 'medium', 'large-v3', 'turbo'] const STT_OPENAI_MODELS = ['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe'] @@ -1977,12 +1983,34 @@ export function render() { ${t('engine.hermesBrowserConfigCommandTimeout')} + + +
+ +
${t('engine.hermesBrowserConfigFootnote')}
@@ -3818,6 +3846,11 @@ export function render() { browserCommandTimeout: el.querySelector('#hm-browser-command-timeout')?.value || '30', browserRecordSessions: !!el.querySelector('#hm-browser-record-sessions')?.checked, browserEngine: el.querySelector('#hm-browser-engine')?.value || 'auto', + browserAllowPrivateUrls: !!el.querySelector('#hm-browser-allow-private-urls')?.checked, + browserAutoLocalForPrivateUrls: !!el.querySelector('#hm-browser-auto-local-for-private-urls')?.checked, + browserCdpUrl: el.querySelector('#hm-browser-cdp-url')?.value || '', + browserDialogPolicy: el.querySelector('#hm-browser-dialog-policy')?.value || 'must_respond', + browserDialogTimeout: el.querySelector('#hm-browser-dialog-timeout')?.value || '300', } browserSaving = true browserError = null diff --git a/src/locales/modules/engine.js b/src/locales/modules/engine.js index 9b2d2c2..db73fd5 100644 --- a/src/locales/modules/engine.js +++ b/src/locales/modules/engine.js @@ -672,7 +672,16 @@ export default { hermesBrowserConfigEngine_auto: _('自动选择', 'Auto select', '自動選擇'), hermesBrowserConfigEngine_lightpanda: _('Lightpanda 快速导航', 'Lightpanda fast navigation', 'Lightpanda 快速導覽'), hermesBrowserConfigEngine_chrome: _('Chrome 完整浏览器', 'Chrome full browser', 'Chrome 完整瀏覽器'), - hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录,请只在需要审计时开启。CDP、Dialog 和 Camofox 高级字段会保留在 raw YAML 中。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Advanced CDP, Dialog, and Camofox fields stay in raw YAML.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄,請只在需要稽核時開啟。CDP、Dialog 和 Camofox 進階欄位會保留在 raw YAML 中。'), + hermesBrowserConfigAllowPrivateUrls: _('允许访问私网和本机地址', 'Allow private and localhost URLs', '允許存取私網和本機位址'), + hermesBrowserConfigAutoLocalForPrivateUrls: _('私网地址自动切到本地浏览器', 'Use local browser automatically for private URLs', '私網位址自動切到本機瀏覽器'), + hermesBrowserConfigCdpUrl: _('CDP 连接地址', 'CDP endpoint URL', 'CDP 連線位址'), + hermesBrowserConfigCdpUrlPlaceholder: _('留空则自动创建浏览器', 'Leave empty to launch browser automatically', '留空則自動建立瀏覽器'), + hermesBrowserConfigDialogPolicy: _('弹窗处理策略', 'Dialog handling policy', '彈窗處理策略'), + hermesBrowserConfigDialogPolicy_must_respond: _('等待用户处理', 'Wait for user response', '等待使用者處理'), + hermesBrowserConfigDialogPolicy_auto_dismiss: _('自动取消弹窗', 'Auto dismiss dialogs', '自動取消彈窗'), + hermesBrowserConfigDialogPolicy_auto_accept: _('自动确认弹窗', 'Auto accept dialogs', '自動確認彈窗'), + hermesBrowserConfigDialogTimeout: _('弹窗等待超时秒数', 'Dialog wait timeout seconds', '彈窗等待逾時秒數'), + hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录,请只在需要审计时开启。允许私网地址会放开 localhost / 192.168 等目标;CDP 地址留空表示自动创建浏览器;Camofox 嵌套高级字段会保留在 raw YAML 中。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Allowing private URLs opens localhost / 192.168-style targets. Empty CDP URL means Hermes launches a browser automatically. Nested Camofox advanced fields stay in raw YAML.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄,請只在需要稽核時開啟。允許私網位址會放開 localhost / 192.168 等目標;CDP 位址留空表示自動建立瀏覽器;Camofox 巢狀進階欄位會保留在 raw YAML 中。'), hermesSttConfigTitle: _('语音转写', 'Speech transcription', '語音轉寫'), hermesSttConfigDesc: _('控制消息平台语音消息是否自动转写,以及本地、OpenAI 和 Mistral 转写模型。适合需要处理语音反馈的渠道。', 'Control automatic voice-message transcription for messaging platforms, plus local, OpenAI, and Mistral transcription models. Useful for channels that receive voice feedback.', '控制訊息平台語音訊息是否自動轉寫,以及本機、OpenAI 和 Mistral 轉寫模型。適合需要處理語音回饋的渠道。'), hermesSttConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'), diff --git a/tests/hermes-browser-config.test.js b/tests/hermes-browser-config.test.js index 59568b7..1bd9119 100644 --- a/tests/hermes-browser-config.test.js +++ b/tests/hermes-browser-config.test.js @@ -14,6 +14,11 @@ test('Hermes 浏览器配置读取会提供上游默认值', () => { browserCommandTimeout: 30, browserRecordSessions: false, browserEngine: 'auto', + browserAllowPrivateUrls: false, + browserAutoLocalForPrivateUrls: true, + browserCdpUrl: '', + browserDialogPolicy: 'must_respond', + browserDialogTimeout: 300, }) }) @@ -24,6 +29,11 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => { command_timeout: 45, record_sessions: true, engine: 'lightpanda', + allow_private_urls: true, + auto_local_for_private_urls: false, + cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo', + dialog_policy: 'auto_accept', + dialog_timeout_s: 120, }, }) @@ -31,6 +41,11 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => { assert.equal(values.browserCommandTimeout, 45) assert.equal(values.browserRecordSessions, true) assert.equal(values.browserEngine, 'lightpanda') + assert.equal(values.browserAllowPrivateUrls, true) + assert.equal(values.browserAutoLocalForPrivateUrls, false) + assert.equal(values.browserCdpUrl, 'ws://127.0.0.1:9222/devtools/browser/demo') + assert.equal(values.browserDialogPolicy, 'auto_accept') + assert.equal(values.browserDialogTimeout, 120) }) test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', () => { @@ -51,6 +66,11 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', ( browserCommandTimeout: '60', browserRecordSessions: true, browserEngine: 'chrome', + browserAllowPrivateUrls: true, + browserAutoLocalForPrivateUrls: false, + browserCdpUrl: 'http://127.0.0.1:9222', + browserDialogPolicy: 'auto_dismiss', + browserDialogTimeout: '45', }) assert.deepEqual(next.model, { provider: 'anthropic' }) @@ -59,11 +79,29 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', ( assert.equal(next.browser.command_timeout, 60) assert.equal(next.browser.record_sessions, true) assert.equal(next.browser.engine, 'chrome') - assert.equal(next.browser.cdp_url, 'ws://127.0.0.1:9222/devtools/browser/demo') + assert.equal(next.browser.allow_private_urls, true) + assert.equal(next.browser.auto_local_for_private_urls, false) + assert.equal(next.browser.cdp_url, 'http://127.0.0.1:9222') + assert.equal(next.browser.dialog_policy, 'auto_dismiss') + assert.equal(next.browser.dialog_timeout_s, 45) assert.deepEqual(next.browser.camofox, { managed_persistence: true }) assert.equal(next.browser.custom_flag, 'keep-browser') }) +test('Hermes 浏览器配置保存空 CDP URL 会移除可选字段', () => { + const next = mergeHermesBrowserConfig({ + browser: { + cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo', + custom_flag: 'keep-browser', + }, + }, { + browserCdpUrl: ' ', + }) + + assert.equal(next.browser.custom_flag, 'keep-browser') + assert.equal(Object.hasOwn(next.browser, 'cdp_url'), false) +}) + test('Hermes 浏览器配置保存会拒绝非法引擎和越界值', () => { assert.throws( () => mergeHermesBrowserConfig({}, { browserEngine: 'firefox' }), @@ -77,4 +115,16 @@ test('Hermes 浏览器配置保存会拒绝非法引擎和越界值', () => { () => mergeHermesBrowserConfig({}, { browserCommandTimeout: '4' }), /browser\.command_timeout/, ) + assert.throws( + () => mergeHermesBrowserConfig({}, { browserDialogPolicy: 'ignore' }), + /browser\.dialog_policy/, + ) + assert.throws( + () => mergeHermesBrowserConfig({}, { browserDialogTimeout: '0' }), + /browser\.dialog_timeout_s/, + ) + assert.throws( + () => mergeHermesBrowserConfig({}, { browserCdpUrl: 123 }), + /browser\.cdp_url/, + ) }) diff --git a/tests/hermes-config-page-ui.test.js b/tests/hermes-config-page-ui.test.js index b9aed76..cc22cf0 100644 --- a/tests/hermes-config-page-ui.test.js +++ b/tests/hermes-config-page-ui.test.js @@ -379,6 +379,11 @@ test('Hermes 配置页会暴露浏览器基础结构化配置字段', () => { 'hm-browser-command-timeout', 'hm-browser-record-sessions', 'hm-browser-engine', + 'hm-browser-allow-private-urls', + 'hm-browser-auto-local-for-private-urls', + 'hm-browser-cdp-url', + 'hm-browser-dialog-policy', + 'hm-browser-dialog-timeout', ]) { assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`) }