ai-dev-browser 0.8.2__tar.gz → 0.8.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/PKG-INFO +1 -1
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/_cli.py +87 -26
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/ax.py +8 -2
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/elements.py +63 -9
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser.egg-info/PKG-INFO +1 -1
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.github/workflows/ci.yml +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.github/workflows/publish.yml +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.gitignore +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.gitmodules +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.pre-commit-config.yaml +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/.secrets.baseline +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/LICENSE +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/README.md +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/_version.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/accessibility.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/animation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/audits.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/autofill.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/background_service.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/bluetooth_emulation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/browser.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/cache_storage.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/cast.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/console.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/crash_report_context.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/css.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/debugger.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/device_access.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/device_orientation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/dom.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/dom_debugger.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/dom_snapshot.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/dom_storage.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/emulation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/event_breakpoints.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/extensions.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/fed_cm.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/fetch.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/file_system.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/headless_experimental.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/heap_profiler.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/indexed_db.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/input_.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/inspector.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/io.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/layer_tree.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/log.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/media.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/memory.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/network.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/overlay.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/page.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/performance.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/performance_timeline.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/preload.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/profiler.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/pwa.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/py.typed +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/runtime.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/schema.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/security.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/service_worker.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/smart_card_emulation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/storage.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/system_info.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/target.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/tethering.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/tracing.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/util.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/web_audio.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/web_authn.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/cdp/web_mcp.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/_case.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/_element.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/_tab.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/_transport.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/browser.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/cdp.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/chrome.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/config.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/connection.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/cookies.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/dialog.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/download.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/human.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/login.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/mouse.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/navigation.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/overlays.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/page.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/port.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/process.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/snapshot.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/storage.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/tabs.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/text_match.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/core/window.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/pool/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/pool/job.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/pool/persistence.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/pool/pool.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/pool/worker.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/profile.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/py.typed +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/_generate.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/browser_list.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/browser_start.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/browser_stop.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/cdp_send.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/click_by_html_id.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/click_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/click_by_text.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/click_by_xpath.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/cookies_list.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/cookies_load.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/cookies_save.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/dialog_respond.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/download.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/drag_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/find_by_html_id.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/find_by_text.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/find_by_xpath.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/focus_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/highlight_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/hover_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/html_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/js_evaluate.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/login_interactive.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/mouse_click.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/mouse_drag.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/mouse_move.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_discover.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_emulate_focus.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_goto.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_html.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_info.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_reload.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_screenshot.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_scroll.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_wait_element.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_wait_ready.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/page_wait_url.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/screenshot_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/select_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/storage_get.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/storage_set.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/tab_close.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/tab_list.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/tab_new.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/tab_switch.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/type_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/type_by_text.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/upload_by_ref.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser/tools/window_set.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser.egg-info/SOURCES.txt +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser.egg-info/dependency_links.txt +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser.egg-info/requires.txt +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/ai_dev_browser.egg-info/top_level.txt +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/pyproject.toml +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/scripts/sync_cdp.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/setup.cfg +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/conftest.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/__init__.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/scenarios_workspace.json +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_dialog_respond.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_find_and_interact_workflows.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_locator_workflows.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_multi_profile_pool.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_page_reload.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_startup_timeout.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_timeout_and_retry.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_workspace_workflows.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/unit/test_case.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/unit/test_cli_param_type.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/unit/test_no_acronym_rot.py +0 -0
- {ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/uv.lock +0 -0
|
@@ -88,6 +88,48 @@ def _parse_docstring_args(docstring: str) -> dict[str, str]:
|
|
|
88
88
|
return args_section
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _parse_docstring_failure(docstring: str) -> str | None:
|
|
92
|
+
"""Extract the body of the Failure: section from a Google-style docstring.
|
|
93
|
+
|
|
94
|
+
Tool authors write failure-path steering once in the docstring's
|
|
95
|
+
`Failure:` section (parallel to `Args:` / `Returns:`). This parser
|
|
96
|
+
extracts it at wrap time; `wrap_core` then auto-injects it as the
|
|
97
|
+
`hint` field on any failure return — SSOT with auto-split across
|
|
98
|
+
`--help` (full docstring, reference surface) and failure `hint`
|
|
99
|
+
(just this section, runtime steering surface).
|
|
100
|
+
|
|
101
|
+
Rationale: guidance about "what to do if this tool fails" placed
|
|
102
|
+
only in the docstring reaches the LLM at most via `--help`, which
|
|
103
|
+
is an on-demand call the LLM rarely makes at invocation-failure
|
|
104
|
+
time. Routing the same authored text into the failure return is
|
|
105
|
+
the only channel with 100% reach at the moment the LLM needs to
|
|
106
|
+
recover.
|
|
107
|
+
|
|
108
|
+
Returns the flat concatenated hint text, or None if no Failure:
|
|
109
|
+
section is present.
|
|
110
|
+
"""
|
|
111
|
+
if not docstring:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
in_failure = False
|
|
115
|
+
lines: list[str] = []
|
|
116
|
+
for raw in docstring.split("\n"):
|
|
117
|
+
stripped = raw.strip()
|
|
118
|
+
# Google-style section headers are single-word lines ending with ':'
|
|
119
|
+
is_section_header = stripped.endswith(":") and len(stripped.split()) == 1
|
|
120
|
+
if is_section_header:
|
|
121
|
+
if stripped == "Failure:":
|
|
122
|
+
in_failure = True
|
|
123
|
+
continue
|
|
124
|
+
if in_failure:
|
|
125
|
+
break # next section ends the Failure block
|
|
126
|
+
elif in_failure:
|
|
127
|
+
lines.append(stripped)
|
|
128
|
+
|
|
129
|
+
text = " ".join(line for line in lines if line).strip()
|
|
130
|
+
return text or None
|
|
131
|
+
|
|
132
|
+
|
|
91
133
|
def _get_param_type(hint) -> type | Callable[[str], bool]:
|
|
92
134
|
"""Convert type hint to argparse type."""
|
|
93
135
|
import types
|
|
@@ -368,25 +410,38 @@ def wrap_core(core_func: Callable, result_key: str = "success") -> Callable:
|
|
|
368
410
|
element_click = as_cli()(wrap_core(click, "clicked"))
|
|
369
411
|
"""
|
|
370
412
|
|
|
413
|
+
failure_hint = _parse_docstring_failure(core_func.__doc__ or "")
|
|
414
|
+
|
|
371
415
|
@functools.wraps(core_func)
|
|
372
416
|
async def wrapper(*args, **kwargs):
|
|
373
417
|
try:
|
|
374
418
|
result = await core_func(*args, **kwargs)
|
|
375
|
-
if isinstance(result, bool):
|
|
376
|
-
if result:
|
|
377
|
-
return {result_key: True}
|
|
378
|
-
else:
|
|
379
|
-
return {"error": "Operation failed"}
|
|
380
|
-
elif isinstance(result, dict):
|
|
381
|
-
# Filter out non-serializable values for CLI output
|
|
382
|
-
return _filter_dict_for_json(result)
|
|
383
|
-
else:
|
|
384
|
-
return {result_key: result}
|
|
385
419
|
except Exception as e:
|
|
386
420
|
# Verbatim message — Python `repr(e)` and CLI stdout stay in
|
|
387
|
-
# lockstep (cli-args-ssot rule
|
|
421
|
+
# lockstep (cli-args-ssot rule 7: never re-render error text
|
|
388
422
|
# in tool files).
|
|
389
|
-
|
|
423
|
+
out: dict = {"error": str(e)}
|
|
424
|
+
if failure_hint:
|
|
425
|
+
out["hint"] = failure_hint
|
|
426
|
+
return out
|
|
427
|
+
|
|
428
|
+
if isinstance(result, bool):
|
|
429
|
+
if result:
|
|
430
|
+
return {result_key: True}
|
|
431
|
+
out = {"error": "Operation failed"}
|
|
432
|
+
if failure_hint:
|
|
433
|
+
out["hint"] = failure_hint
|
|
434
|
+
return out
|
|
435
|
+
if isinstance(result, dict):
|
|
436
|
+
filtered = _filter_dict_for_json(result)
|
|
437
|
+
# Auto-inject failure hint when the tool reports failure via
|
|
438
|
+
# result_key=False. Pairs with cli-args-ssot Rule 5a: failure
|
|
439
|
+
# steering goes through the return channel (100% reach at
|
|
440
|
+
# invocation time), not docstring (only reaches on --help).
|
|
441
|
+
if filtered.get(result_key) is False and failure_hint:
|
|
442
|
+
filtered.setdefault("hint", failure_hint)
|
|
443
|
+
return filtered
|
|
444
|
+
return {result_key: result}
|
|
390
445
|
|
|
391
446
|
return wrapper
|
|
392
447
|
|
|
@@ -411,24 +466,30 @@ def wrap_core_sync(core_func: Callable, result_key: str = "success") -> Callable
|
|
|
411
466
|
browser_start = as_cli(requires_tab=False)(wrap_core_sync(browser_start, "port"))
|
|
412
467
|
"""
|
|
413
468
|
|
|
469
|
+
failure_hint = _parse_docstring_failure(core_func.__doc__ or "")
|
|
470
|
+
|
|
414
471
|
@functools.wraps(core_func)
|
|
415
472
|
def wrapper(*args, **kwargs):
|
|
416
473
|
try:
|
|
417
474
|
result = core_func(*args, **kwargs)
|
|
418
|
-
if isinstance(result, bool):
|
|
419
|
-
if result:
|
|
420
|
-
return {result_key: True}
|
|
421
|
-
else:
|
|
422
|
-
return {"error": "Operation failed"}
|
|
423
|
-
elif isinstance(result, dict):
|
|
424
|
-
# Filter out non-serializable values for CLI output
|
|
425
|
-
return _filter_dict_for_json(result)
|
|
426
|
-
else:
|
|
427
|
-
return {result_key: result}
|
|
428
475
|
except Exception as e:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return
|
|
476
|
+
out: dict = {"error": str(e)}
|
|
477
|
+
if failure_hint:
|
|
478
|
+
out["hint"] = failure_hint
|
|
479
|
+
return out
|
|
480
|
+
|
|
481
|
+
if isinstance(result, bool):
|
|
482
|
+
if result:
|
|
483
|
+
return {result_key: True}
|
|
484
|
+
out = {"error": "Operation failed"}
|
|
485
|
+
if failure_hint:
|
|
486
|
+
out["hint"] = failure_hint
|
|
487
|
+
return out
|
|
488
|
+
if isinstance(result, dict):
|
|
489
|
+
filtered = _filter_dict_for_json(result)
|
|
490
|
+
if filtered.get(result_key) is False and failure_hint:
|
|
491
|
+
filtered.setdefault("hint", failure_hint)
|
|
492
|
+
return filtered
|
|
493
|
+
return {result_key: result}
|
|
433
494
|
|
|
434
495
|
return wrapper
|
|
@@ -309,8 +309,14 @@ async def click_by_ref(
|
|
|
309
309
|
dict with clicked status, element info, and navigation feedback:
|
|
310
310
|
`{clicked, ref, role, name, url_before, url_after, title_after, navigated}`.
|
|
311
311
|
`navigated=True` means the top-level URL changed after the click
|
|
312
|
-
(SPA route change or full page load).
|
|
313
|
-
|
|
312
|
+
(SPA route change or full page load).
|
|
313
|
+
|
|
314
|
+
Failure:
|
|
315
|
+
Ref is stale (page navigated or element was removed between
|
|
316
|
+
the `page_discover` / `find_by_text` call that returned it
|
|
317
|
+
and this click). Re-run `page_discover` or `find_by_text` to
|
|
318
|
+
get a fresh ref, or use a stable locator — `click_by_html_id`
|
|
319
|
+
/ `click_by_xpath` / `click_by_text`.
|
|
314
320
|
|
|
315
321
|
Example:
|
|
316
322
|
# First page_discover elements
|
|
@@ -432,6 +432,15 @@ async def page_wait_element(
|
|
|
432
432
|
|
|
433
433
|
Returns:
|
|
434
434
|
dict with found, elapsed, message
|
|
435
|
+
|
|
436
|
+
Failure:
|
|
437
|
+
Element didn't appear within `timeout` seconds. Try a longer
|
|
438
|
+
timeout if the page is slow; a broader locator (partial text
|
|
439
|
+
instead of exact, less-specific CSS selector); or confirm the
|
|
440
|
+
element is expected on this page via `page_discover`. For
|
|
441
|
+
iframe-embedded targets with a text locator, note that
|
|
442
|
+
text-based wait scans the top frame only — use
|
|
443
|
+
`find_by_text` + `click_by_ref` pattern instead of waiting.
|
|
435
444
|
"""
|
|
436
445
|
result = await _wait_for_element(tab, text=text, selector=selector, timeout=timeout)
|
|
437
446
|
|
|
@@ -464,13 +473,6 @@ async def click_by_text(
|
|
|
464
473
|
(form-validation error rendering, captcha pixels for OCR, final
|
|
465
474
|
result view for the user).
|
|
466
475
|
|
|
467
|
-
**Top-frame only.** Unlike `click_by_html_id` / `click_by_xpath`
|
|
468
|
-
(which recurse through `window.frames`) and `find_by_text` (which
|
|
469
|
-
scans the full AX tree), this goes through CDP `DOM.performSearch`
|
|
470
|
-
and won't find text inside an iframe (nav menus in `<iframe>`,
|
|
471
|
-
embedded widgets). For those, use `find_by_text` → `click_by_ref`
|
|
472
|
-
or `click_by_xpath` / `click_by_html_id`.
|
|
473
|
-
|
|
474
476
|
Prefer when text is unique / unambiguous and top-frame. For elements
|
|
475
477
|
you already identified via `page_discover`, use `click_by_ref`.
|
|
476
478
|
|
|
@@ -483,8 +485,17 @@ async def click_by_text(
|
|
|
483
485
|
Returns:
|
|
484
486
|
dict with clicked, text, url_before, url_after, title_after, navigated.
|
|
485
487
|
`navigated=True` means the top-level URL changed after the click
|
|
486
|
-
(SPA route change or full page load).
|
|
487
|
-
|
|
488
|
+
(SPA route change or full page load).
|
|
489
|
+
|
|
490
|
+
Failure:
|
|
491
|
+
Not found in the top frame. This tool uses CDP
|
|
492
|
+
`DOM.performSearch` on the main document only — it won't find
|
|
493
|
+
text inside iframes (nav menus in `<iframe>`, embedded
|
|
494
|
+
widgets). Try `find_by_text` → `click_by_ref` (scans
|
|
495
|
+
same-origin iframes and falls back to non-interactable AX
|
|
496
|
+
nodes like `<div onclick>`), or `click_by_xpath` /
|
|
497
|
+
`click_by_html_id` which recurse through `window.frames`
|
|
498
|
+
natively.
|
|
488
499
|
|
|
489
500
|
Example:
|
|
490
501
|
click_by_text("登录")
|
|
@@ -551,6 +562,15 @@ async def find_by_text(
|
|
|
551
562
|
Returns:
|
|
552
563
|
dict: `{found: True, ref, role, name, x, y, ...}` on hit,
|
|
553
564
|
`{found: False, text}` otherwise.
|
|
565
|
+
|
|
566
|
+
Failure:
|
|
567
|
+
Text not found in main frame or any same-origin iframe, in
|
|
568
|
+
either the interactable or fallback tier. Check spelling /
|
|
569
|
+
case (match is case-insensitive substring); try a shorter
|
|
570
|
+
substring; or switch locator — `find_by_html_id` /
|
|
571
|
+
`find_by_xpath` if a DOM-level locator is known. For a broad
|
|
572
|
+
survey of what's on the page, run `page_discover` without a
|
|
573
|
+
text filter. Cross-origin iframes are not scanned.
|
|
554
574
|
"""
|
|
555
575
|
from .snapshot import page_discover
|
|
556
576
|
|
|
@@ -606,6 +626,13 @@ async def type_by_text(
|
|
|
606
626
|
Returns:
|
|
607
627
|
dict with typed status
|
|
608
628
|
|
|
629
|
+
Failure:
|
|
630
|
+
Input with this accessible name not found in the top frame.
|
|
631
|
+
For iframe-embedded inputs, use `find_by_text` (AX-tree scan,
|
|
632
|
+
iframe-aware) to get a ref, then `type_by_ref`. If the input
|
|
633
|
+
lacks an accessible name, locate by html id with
|
|
634
|
+
`find_by_html_id` → `type_by_ref`.
|
|
635
|
+
|
|
609
636
|
Example:
|
|
610
637
|
type_by_text(name="用户名", text="myusername")
|
|
611
638
|
type_by_text(name="Search", text="query", clear=True)
|
|
@@ -822,6 +849,12 @@ async def find_by_html_id(tab: Tab, html_id: str) -> dict:
|
|
|
822
849
|
dict: `{found: true, tag, text, visible, attrs}` on hit,
|
|
823
850
|
`{found: false}` otherwise.
|
|
824
851
|
|
|
852
|
+
Failure:
|
|
853
|
+
No element with this id in any same-origin frame. Use
|
|
854
|
+
`page_discover` to see the ids actually present, or switch
|
|
855
|
+
locator — `find_by_text` if you know the visible label,
|
|
856
|
+
`find_by_xpath` for attribute-predicate / positional lookups.
|
|
857
|
+
|
|
825
858
|
Example:
|
|
826
859
|
result = await find_by_html_id(tab, "submit-btn")
|
|
827
860
|
if result["found"] and result["visible"]:
|
|
@@ -869,6 +902,13 @@ async def click_by_html_id(tab: Tab, html_id: str) -> dict:
|
|
|
869
902
|
Returns:
|
|
870
903
|
dict: `{clicked, html_id, url_before, url_after, title_after, navigated, error?}`.
|
|
871
904
|
`navigated=True` means the top-level URL changed after the click.
|
|
905
|
+
|
|
906
|
+
Failure:
|
|
907
|
+
No element with this id found in any same-origin frame. Run
|
|
908
|
+
`find_by_html_id` first to verify existence and get attrs, or
|
|
909
|
+
switch locator — `click_by_text` (top frame only, use
|
|
910
|
+
`find_by_text` → `click_by_ref` for iframe targets) or
|
|
911
|
+
`click_by_xpath` for attribute / positional predicates.
|
|
872
912
|
"""
|
|
873
913
|
url_before_state = await _capture_page_state(tab)
|
|
874
914
|
url_before = url_before_state.get("url", "")
|
|
@@ -927,6 +967,14 @@ async def find_by_xpath(tab: Tab, xpath: str) -> dict:
|
|
|
927
967
|
Returns:
|
|
928
968
|
dict: `{found: true, tag, text, visible, attrs}` on hit,
|
|
929
969
|
`{found: false}` otherwise.
|
|
970
|
+
|
|
971
|
+
Failure:
|
|
972
|
+
XPath returned no match in any same-origin frame. Shorten the
|
|
973
|
+
expression (e.g. `//button` instead of
|
|
974
|
+
`//button[@data-role='x']`) to verify the broader target
|
|
975
|
+
exists, or switch locator — `find_by_text` by visible label,
|
|
976
|
+
`find_by_html_id` if an id is known, or `page_discover` for a
|
|
977
|
+
structural survey.
|
|
930
978
|
"""
|
|
931
979
|
expr = """
|
|
932
980
|
(function(xpath) {
|
|
@@ -975,6 +1023,12 @@ async def click_by_xpath(tab: Tab, xpath: str) -> dict:
|
|
|
975
1023
|
|
|
976
1024
|
Returns:
|
|
977
1025
|
dict: `{clicked, xpath, url_before, url_after, title_after, navigated, error?}`.
|
|
1026
|
+
|
|
1027
|
+
Failure:
|
|
1028
|
+
XPath returned no match in any same-origin frame. Verify with
|
|
1029
|
+
`find_by_xpath` first (returns the matched element's attrs
|
|
1030
|
+
without clicking), or switch locator — `click_by_text` /
|
|
1031
|
+
`click_by_html_id`, or `page_discover` → `click_by_ref`.
|
|
978
1032
|
"""
|
|
979
1033
|
url_before_state = await _capture_page_state(tab)
|
|
980
1034
|
url_before = url_before_state.get("url", "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_dev_browser-0.8.2 → ai_dev_browser-0.8.3}/tests/integration/test_find_and_interact_workflows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|