diff --git a/app/gui/tabs/settings.py b/app/gui/tabs/settings.py index 82096b4..0f1e763 100644 --- a/app/gui/tabs/settings.py +++ b/app/gui/tabs/settings.py @@ -3,6 +3,7 @@ from __future__ import annotations from contextlib import contextmanager +import os from ... import ai as ai_module from ..widgets import * @@ -165,6 +166,15 @@ class SettingsTab(QWidget): self.jpg_quality_spin.setRange(1, 100) self.chrome_path_edit = QLineEdit() self.chrome_path_edit.setObjectName("chromePathEdit") + self.chrome_path_browse_button = QPushButton("选择...") + self.chrome_path_browse_button.setObjectName("chromePathBrowseButton") + self.chrome_path_widget = QWidget() + self.chrome_path_widget.setObjectName("chromePathWidget") + chrome_path_layout = QHBoxLayout(self.chrome_path_widget) + chrome_path_layout.setContentsMargins(0, 0, 0, 0) + chrome_path_layout.setSpacing(8) + chrome_path_layout.addWidget(self.chrome_path_edit, 1) + chrome_path_layout.addWidget(self.chrome_path_browse_button) self.user_data_root_edit = QLineEdit() self.user_data_root_edit.setObjectName("userDataRootEdit") self.user_data_root_edit.setEnabled(False) @@ -271,7 +281,7 @@ class SettingsTab(QWidget): path_form = self._three_column_form( [ - ("Chrome路径", self.chrome_path_edit, True), + ("Chrome路径", self.chrome_path_widget, True), ("默认调试端口", self.default_debug_port_spin), ("调试端口范围", port_range_widget), ("CDP就绪超时(秒)", self.cdp_ready_timeout_spin), @@ -403,6 +413,7 @@ class SettingsTab(QWidget): self.resolution_combo.currentIndexChanged.connect( self._update_response_timeout_label ) + self.chrome_path_browse_button.clicked.connect(self.browse_chrome_path) self.save_config_button.clicked.connect(self.save_app_settings) self._connect_dirty_signals() @@ -533,6 +544,23 @@ class SettingsTab(QWidget): def is_dirty(self): return self._dirty + def browse_chrome_path(self, checked=False): + current = self.chrome_path_edit.text().strip() + initial_dir = "" + if current: + parent_dir = os.path.dirname(os.path.expanduser(current)) + if parent_dir and os.path.isdir(parent_dir): + initial_dir = parent_dir + path, _selected_filter = QFileDialog.getOpenFileName( + self, + "选择 Chrome 程序", + initial_dir, + "Chrome 程序 (chrome.exe);;可执行文件 (*.exe);;所有文件 (*)", + ) + if not path: + return + self.chrome_path_edit.setText(path) + def discard_unsaved_changes(self): try: saved = appconfig.load_config(self.config_path) diff --git a/docs/tasks/T-557.md b/docs/tasks/T-557.md index 82a7847..147b4b8 100644 --- a/docs/tasks/T-557.md +++ b/docs/tasks/T-557.md @@ -3,7 +3,7 @@ id: T-557 title: ⑤设置 Chrome 路径增加文件选择按钮 phase: 7 deps: [T-539, T-543] -status: TODO +status: DONE created: 2026-07-08 --- @@ -44,3 +44,11 @@ created: 2026-07-08 ## 边界(不改什么) 只改⑤设置页 Chrome 路径的 GUI 选择入口和测试;不改 `config.json` schema、不改 `appconfig.chrome_path()`、不改 `app/chrome.py` 启动参数、不改④账号管理、不改 CDP/Shopee 逻辑、不引入自动搜索/自动安装/自动启动 Chrome。 + +## 执行记录 + +- 2026-07-08:⑤设置页「Chrome路径」改为输入框 + `选择...` 按钮的横向控件,按钮对象名为 `chromePathBrowseButton`。 +- 2026-07-08:点击 `选择...` 后调用 `QFileDialog.getOpenFileName()`,中文标题为「选择 Chrome 程序」,过滤 `chrome.exe` / `*.exe`;选中文件后回填 `chromePathEdit` 并沿用现有未保存状态机制,取消不改变路径。 +- 2026-07-08:保存逻辑仍复用现有 `save_app_settings()`,所选路径写入 `config.json` 的 `chrome_path`,未改配置 schema、Chrome 启动、账号管理或 CDP。 +- 2026-07-08:更新 GUI 单测,覆盖按钮存在、布局、选择回填、取消不改值、选择后保存写入配置。 +- 验证通过:`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`、`git diff --check`。 diff --git a/tests/test_gui.py b/tests/test_gui.py index d7fc47a..2ca0bf1 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -733,9 +733,61 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertFalse(hidden_widget.isEnabled()) self.assertEqual(-1, tab.infrastructure_form_layout.indexOf(hidden_widget)) self.assertGreaterEqual( - tab.infrastructure_form_layout.indexOf(tab.chrome_path_edit), + tab.infrastructure_form_layout.indexOf(tab.chrome_path_widget), 0, ) + self.assertEqual("选择...", tab.chrome_path_browse_button.text()) + self.assertEqual("chromePathBrowseButton", tab.chrome_path_browse_button.objectName()) + self.assertIs( + tab.chrome_path_widget.layout().itemAt(0).widget(), + tab.chrome_path_edit, + ) + self.assertIs( + tab.chrome_path_widget.layout().itemAt(1).widget(), + tab.chrome_path_browse_button, + ) + + self.assert_removed(temp_dir) + + def test_settings_tab_browses_chrome_path(self): + with self.make_temp_dir() as temp_dir: + cfg = self.make_config(temp_dir) + statuses = [] + tab = SettingsTab(config=cfg, status_callback=statuses.append) + self.addCleanup(tab.close) + selected_path = os.path.join(temp_dir, "Chrome", "chrome.exe") + os.makedirs(os.path.dirname(selected_path), exist_ok=True) + + self.assertFalse(tab.is_dirty()) + with mock.patch( + "app.gui.tabs.settings.QFileDialog.getOpenFileName", + return_value=(selected_path, "Chrome 程序 (chrome.exe)"), + ) as browse: + tab.chrome_path_browse_button.click() + + browse.assert_called_once() + self.assertEqual("选择 Chrome 程序", browse.call_args[0][1]) + self.assertEqual(selected_path, tab.chrome_path_edit.text()) + self.assertTrue(tab.is_dirty()) + self.assertFalse(tab.unsaved_changes_label.isHidden()) + + with mock.patch("app.gui.QMessageBox.information") as info: + self.assertTrue(tab.save_app_settings()) + info.assert_called_once_with(tab, "保存设置", "设置已保存") + saved = appconfig.load_config(cfg["config_path"]) + self.assertEqual(selected_path, saved["chrome_path"]) + self.assertFalse(tab.is_dirty()) + + tab._set_dirty(False) + previous_path = tab.chrome_path_edit.text() + with mock.patch( + "app.gui.tabs.settings.QFileDialog.getOpenFileName", + return_value=("", ""), + ): + tab.chrome_path_browse_button.click() + + self.assertEqual(previous_path, tab.chrome_path_edit.text()) + self.assertFalse(tab.is_dirty()) self.assert_removed(temp_dir)