inspect-ai 0.3.70__py3-none-any.whl → 0.3.71__py3-none-any.whl
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.
- inspect_ai/_cli/eval.py +14 -8
- inspect_ai/_display/core/display.py +2 -0
- inspect_ai/_display/core/footer.py +13 -3
- inspect_ai/_display/plain/display.py +6 -2
- inspect_ai/_display/rich/display.py +19 -6
- inspect_ai/_display/textual/app.py +6 -1
- inspect_ai/_display/textual/display.py +4 -0
- inspect_ai/_display/textual/widgets/transcript.py +10 -6
- inspect_ai/_eval/task/run.py +5 -8
- inspect_ai/_util/content.py +20 -1
- inspect_ai/_util/transcript.py +10 -4
- inspect_ai/_util/working.py +4 -0
- inspect_ai/_view/www/App.css +6 -0
- inspect_ai/_view/www/dist/assets/index.css +115 -87
- inspect_ai/_view/www/dist/assets/index.js +5324 -2276
- inspect_ai/_view/www/eslint.config.mjs +24 -1
- inspect_ai/_view/www/log-schema.json +283 -20
- inspect_ai/_view/www/package.json +8 -3
- inspect_ai/_view/www/src/App.tsx +2 -2
- inspect_ai/_view/www/src/components/AnsiDisplay.tsx +4 -3
- inspect_ai/_view/www/src/components/Card.tsx +9 -8
- inspect_ai/_view/www/src/components/DownloadButton.tsx +2 -1
- inspect_ai/_view/www/src/components/EmptyPanel.tsx +2 -2
- inspect_ai/_view/www/src/components/ErrorPanel.tsx +4 -3
- inspect_ai/_view/www/src/components/ExpandablePanel.tsx +13 -5
- inspect_ai/_view/www/src/components/FindBand.tsx +3 -3
- inspect_ai/_view/www/src/components/HumanBaselineView.tsx +3 -3
- inspect_ai/_view/www/src/components/LabeledValue.tsx +5 -4
- inspect_ai/_view/www/src/components/LargeModal.tsx +18 -13
- inspect_ai/_view/www/src/components/{LightboxCarousel.css → LightboxCarousel.module.css} +22 -18
- inspect_ai/_view/www/src/components/LightboxCarousel.tsx +36 -27
- inspect_ai/_view/www/src/components/MessageBand.tsx +2 -1
- inspect_ai/_view/www/src/components/NavPills.tsx +9 -8
- inspect_ai/_view/www/src/components/ProgressBar.tsx +2 -1
- inspect_ai/_view/www/src/components/TabSet.tsx +21 -15
- inspect_ai/_view/www/src/index.tsx +2 -2
- inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +11 -9
- inspect_ai/_view/www/src/metadata/MetaDataView.tsx +3 -2
- inspect_ai/_view/www/src/metadata/MetadataGrid.module.css +1 -0
- inspect_ai/_view/www/src/metadata/RenderedContent.tsx +16 -0
- inspect_ai/_view/www/src/plan/DatasetDetailView.tsx +3 -2
- inspect_ai/_view/www/src/plan/DetailStep.tsx +2 -1
- inspect_ai/_view/www/src/plan/PlanCard.tsx +2 -5
- inspect_ai/_view/www/src/plan/PlanDetailView.tsx +6 -9
- inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +2 -1
- inspect_ai/_view/www/src/plan/SolverDetailView.tsx +3 -3
- inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +2 -2
- inspect_ai/_view/www/src/samples/SampleDialog.tsx +3 -3
- inspect_ai/_view/www/src/samples/SampleDisplay.tsx +2 -2
- inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +2 -2
- inspect_ai/_view/www/src/samples/SamplesTools.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +3 -19
- inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessageRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatView.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +22 -7
- inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +35 -6
- inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -2
- inspect_ai/_view/www/src/samples/chat/messages.ts +15 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +13 -4
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.module.css +2 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +18 -19
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.module.css +1 -1
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +4 -3
- inspect_ai/_view/www/src/samples/chat/tools/ToolTitle.tsx +2 -2
- inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +2 -3
- inspect_ai/_view/www/src/samples/error/SampleErrorView.tsx +3 -2
- inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleHeader.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleList.tsx +57 -45
- inspect_ai/_view/www/src/samples/list/SampleRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleSeparator.tsx +2 -1
- inspect_ai/_view/www/src/samples/sample-tools/EpochFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +4 -3
- inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +2 -5
- inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +2 -1
- inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/ApprovalEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.module.css +4 -0
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.tsx +12 -2
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +1 -1
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +25 -28
- inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +5 -4
- inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +8 -7
- inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +3 -3
- inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +18 -14
- inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -5
- inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +34 -15
- inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventRow.tsx +3 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventSection.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.module.css +28 -0
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.tsx +115 -0
- inspect_ai/_view/www/src/samples/transcript/event/utils.ts +29 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateDiffView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +3 -3
- inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +11 -8
- inspect_ai/_view/www/src/types/log.d.ts +129 -34
- inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +6 -10
- inspect_ai/_view/www/src/usage/ModelUsagePanel.module.css +4 -0
- inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +32 -9
- inspect_ai/_view/www/src/usage/TokenTable.tsx +4 -6
- inspect_ai/_view/www/src/usage/UsageCard.tsx +2 -1
- inspect_ai/_view/www/src/utils/format.ts +1 -1
- inspect_ai/_view/www/src/utils/json.ts +24 -0
- inspect_ai/_view/www/src/workspace/WorkSpace.tsx +6 -5
- inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +9 -2
- inspect_ai/_view/www/src/workspace/error/TaskErrorPanel.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +3 -3
- inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +4 -3
- inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +5 -4
- inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +5 -8
- inspect_ai/_view/www/src/workspace/sidebar/EvalStatus.tsx +5 -4
- inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -2
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +2 -5
- inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +12 -11
- inspect_ai/_view/www/yarn.lock +241 -5
- inspect_ai/log/_condense.py +3 -0
- inspect_ai/log/_recorders/eval.py +6 -1
- inspect_ai/log/_transcript.py +58 -1
- inspect_ai/model/__init__.py +2 -0
- inspect_ai/model/_call_tools.py +7 -0
- inspect_ai/model/_chat_message.py +22 -7
- inspect_ai/model/_conversation.py +10 -8
- inspect_ai/model/_generate_config.py +25 -4
- inspect_ai/model/_model.py +133 -57
- inspect_ai/model/_model_output.py +3 -0
- inspect_ai/model/_openai.py +106 -40
- inspect_ai/model/_providers/anthropic.py +134 -26
- inspect_ai/model/_providers/google.py +27 -8
- inspect_ai/model/_providers/groq.py +9 -4
- inspect_ai/model/_providers/openai.py +57 -4
- inspect_ai/model/_providers/openai_o1.py +10 -0
- inspect_ai/model/_providers/providers.py +1 -1
- inspect_ai/model/_reasoning.py +15 -2
- inspect_ai/scorer/_model.py +23 -19
- inspect_ai/solver/_human_agent/agent.py +14 -10
- inspect_ai/solver/_human_agent/commands/__init__.py +7 -3
- inspect_ai/solver/_human_agent/commands/submit.py +76 -30
- inspect_ai/tool/__init__.py +2 -0
- inspect_ai/tool/_tool.py +3 -1
- inspect_ai/tool/_tools/_computer/_resources/tool/_run.py +1 -1
- inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +8 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +24 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +25 -0
- inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +5 -6
- inspect_ai/tool/_tools/_web_browser/_resources/README.md +10 -11
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +71 -0
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +323 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +5 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +279 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +9 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +293 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +94 -0
- inspect_ai/tool/_tools/_web_browser/_resources/constants.py +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +50 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +31 -359
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +280 -0
- inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +65 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +146 -0
- inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +180 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +15 -9
- inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +15 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +44 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +39 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +198 -48
- inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +26 -25
- inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +178 -39
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +38 -19
- inspect_ai/util/__init__.py +2 -1
- inspect_ai/util/_display.py +12 -0
- inspect_ai/util/_sandbox/events.py +55 -21
- inspect_ai/util/_sandbox/self_check.py +131 -43
- inspect_ai/util/_subtask.py +11 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/METADATA +1 -1
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/RECORD +197 -182
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/WHEEL +1 -1
- inspect_ai/_view/www/node_modules/flatted/python/flatted.py +0 -149
- inspect_ai/_view/www/node_modules/flatted/python/test.py +0 -63
- inspect_ai/_view/www/src/components/VirtualList.module.css +0 -19
- inspect_ai/_view/www/src/components/VirtualList.tsx +0 -292
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_node.py +0 -312
- inspect_ai/tool/_tools/_web_browser/_resources/dm_env_servicer.py +0 -275
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.png +0 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_node.py +0 -176
- inspect_ai/tool/_tools/_web_browser/_resources/test_dm_env_servicer.py +0 -135
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_environment.py +0 -71
- inspect_ai/tool/_tools/_web_browser/_resources/web_environment.py +0 -184
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=64", "setuptools_scm[toml]>=8"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[tool.setuptools_scm]
|
6
|
+
|
7
|
+
[tool.setuptools.packages.find]
|
8
|
+
where = ["."]
|
9
|
+
include = ["inspect_ai*"]
|
10
|
+
|
11
|
+
[tool.ruff]
|
12
|
+
src = ["."]
|
13
|
+
|
14
|
+
[tool.ruff.lint]
|
15
|
+
select = [
|
16
|
+
"E", # pycodestyle errors
|
17
|
+
"W", # pycodestyle warnings
|
18
|
+
"F", # flake8
|
19
|
+
"D", # pydocstyle
|
20
|
+
"I", # isort
|
21
|
+
"SIM101", # duplicate isinstance
|
22
|
+
"UP038", # non-pep604-isinstance
|
23
|
+
# "RET", # flake8-return
|
24
|
+
# "RUF", # ruff rules
|
25
|
+
]
|
26
|
+
ignore = ["E203", "E501", "D10", "D212", "D415"]
|
27
|
+
|
28
|
+
[tool.ruff.lint.pydocstyle]
|
29
|
+
convention = "google"
|
30
|
+
|
31
|
+
[tool.pytest.ini_options]
|
32
|
+
minversion = "7.0"
|
33
|
+
addopts = "-rA --doctest-modules --color=yes"
|
34
|
+
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
|
35
|
+
asyncio_mode = "auto"
|
36
|
+
asyncio_default_fixture_loop_scope = "function"
|
37
|
+
log_level = "warning"
|
38
|
+
|
39
|
+
[tool.mypy]
|
40
|
+
warn_unused_ignores = true
|
41
|
+
no_implicit_reexport = true
|
42
|
+
strict_equality = true
|
43
|
+
warn_redundant_casts = true
|
44
|
+
warn_unused_configs = true
|
45
|
+
disallow_any_explicit = true
|
46
|
+
disallow_any_generics = true
|
47
|
+
disallow_subclassing_any = true
|
48
|
+
plugins=["pydantic.mypy"]
|
49
|
+
|
50
|
+
|
51
|
+
[tool.pydantic-mypy]
|
52
|
+
init_forbid_extra = true
|
53
|
+
init_typed = true
|
54
|
+
|
55
|
+
[tool.check-wheel-contents]
|
56
|
+
ignore = ["W002", "W009"]
|
57
|
+
|
58
|
+
[project]
|
59
|
+
name = "web_browser_tool_container"
|
60
|
+
requires-python = ">=3.10"
|
61
|
+
dynamic = ["version", "dependencies"]
|
62
|
+
|
63
|
+
|
64
|
+
[project.optional-dependencies]
|
65
|
+
dev = ["pytest"]
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
|
4
|
+
class Rectangle:
|
5
|
+
def __init__(self, left: float, top: float, width: float, height: float):
|
6
|
+
self._left = int(left)
|
7
|
+
self._width = int(width)
|
8
|
+
self._top = int(top)
|
9
|
+
self._height = int(height)
|
10
|
+
|
11
|
+
@classmethod
|
12
|
+
def _from_left_right_top_bottom(
|
13
|
+
cls, left: float, right: float, top: float, bottom: float
|
14
|
+
) -> Rectangle:
|
15
|
+
return cls(left, top, right - left, bottom - top)
|
16
|
+
|
17
|
+
@property
|
18
|
+
def _right(self) -> int:
|
19
|
+
return self._left + self._width
|
20
|
+
|
21
|
+
@property
|
22
|
+
def _bottom(self) -> int:
|
23
|
+
return self._top + self._height
|
24
|
+
|
25
|
+
@property
|
26
|
+
def center_x(self) -> int:
|
27
|
+
return self._left + self._width // 2
|
28
|
+
|
29
|
+
@property
|
30
|
+
def center_y(self) -> int:
|
31
|
+
return self._top + self._height // 2
|
32
|
+
|
33
|
+
@property
|
34
|
+
def has_area(self) -> bool:
|
35
|
+
return self._width > 0 and self._height > 0
|
36
|
+
|
37
|
+
def __str__(self) -> str:
|
38
|
+
return f"({self._left}, {self._top}, {self._width}, {self._height})"
|
39
|
+
|
40
|
+
def scale(self, scale: float) -> Rectangle:
|
41
|
+
return self._from_left_right_top_bottom(
|
42
|
+
self._left * scale,
|
43
|
+
self._right * scale,
|
44
|
+
self._top * scale,
|
45
|
+
self._bottom * scale,
|
46
|
+
)
|
47
|
+
|
48
|
+
def overlaps(self, other: Rectangle) -> bool:
|
49
|
+
"""Returns if the two rectangles intersect."""
|
50
|
+
return (
|
51
|
+
other._left < self._right # pylint: disable=protected-access
|
52
|
+
and other._right > self._left # pylint: disable=protected-access
|
53
|
+
and other._top < self._bottom # pylint: disable=protected-access
|
54
|
+
and other._bottom > self._top # pylint: disable=protected-access
|
55
|
+
)
|
56
|
+
|
57
|
+
def within(self, other: Rectangle) -> bool:
|
58
|
+
"""Returns if this rectangle is within the other rectangle."""
|
59
|
+
return (
|
60
|
+
other._left <= self._left # pylint: disable=protected-access
|
61
|
+
and other._right >= self._left # pylint: disable=protected-access
|
62
|
+
and other._top <= self._bottom # pylint: disable=protected-access
|
63
|
+
and other._bottom >= self._top # pylint: disable=protected-access
|
64
|
+
)
|
@@ -0,0 +1,146 @@
|
|
1
|
+
"""
|
2
|
+
This module provides helper functions and classes for making strongly typed RPC calls.
|
3
|
+
|
4
|
+
The module is designed to be generic and should not contain any use case specific logic.
|
5
|
+
It uses the `httpx` library for making HTTP requests, `jsonrpcclient` for handling JSON-RPC responses,
|
6
|
+
and `pydantic` for validating and parsing response data into Python objects.
|
7
|
+
|
8
|
+
Classes:
|
9
|
+
RPCError: Custom exception for handling RPC errors.
|
10
|
+
|
11
|
+
Functions:
|
12
|
+
typed_rpc_call: Makes a typed RPC call and returns the response as a Pydantic model.
|
13
|
+
"""
|
14
|
+
|
15
|
+
from typing import Generic, Mapping, Type, TypedDict, TypeVar
|
16
|
+
|
17
|
+
from httpx import (
|
18
|
+
URL,
|
19
|
+
Client,
|
20
|
+
ConnectError,
|
21
|
+
ConnectTimeout,
|
22
|
+
HTTPStatusError,
|
23
|
+
ReadTimeout,
|
24
|
+
Response,
|
25
|
+
)
|
26
|
+
from jsonrpcclient import Error, Ok, parse, request
|
27
|
+
from pydantic import BaseModel
|
28
|
+
from tenacity import (
|
29
|
+
retry,
|
30
|
+
retry_if_exception,
|
31
|
+
stop_after_attempt,
|
32
|
+
stop_after_delay,
|
33
|
+
wait_exponential_jitter,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class RPCError(RuntimeError):
|
38
|
+
def __init__(self, *, code: int, message: str, data: object):
|
39
|
+
self.code = code
|
40
|
+
self.message = message
|
41
|
+
self.data = data
|
42
|
+
super().__init__(f"RPCError {code}: {message}")
|
43
|
+
|
44
|
+
def __str__(self):
|
45
|
+
return f"RPCError {self.code}: {self.message} (data: {self.data})"
|
46
|
+
|
47
|
+
|
48
|
+
TBaseModel = TypeVar("TBaseModel", bound=BaseModel)
|
49
|
+
|
50
|
+
|
51
|
+
def rpc_call(
|
52
|
+
url: URL | str,
|
53
|
+
method: str,
|
54
|
+
params: dict[str, object] | None,
|
55
|
+
response_class: Type[TBaseModel],
|
56
|
+
) -> TBaseModel:
|
57
|
+
"""
|
58
|
+
Makes an RPC call to the specified URL with the given method and parameters, and returns the response as a parsed and validated instance of the specified response class.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
url (URL | str): The URL to which the RPC call is made.
|
62
|
+
method (str): The RPC method to be called.
|
63
|
+
response_class (Type[TBaseModel]): The class to which the response should be deserialized.
|
64
|
+
params (dict[str, object] | None, optional): The parameters to be sent with the RPC call. Defaults to None.
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
TBaseModel: An instance of the response class containing the result of the RPC call.
|
68
|
+
|
69
|
+
Raises:
|
70
|
+
RPCError: If the RPC call returns an error response.
|
71
|
+
RuntimeError: If an unexpected response is received.
|
72
|
+
"""
|
73
|
+
match parse(_retrying_post(url, request(method, params)).json()):
|
74
|
+
case Ok(ok_result):
|
75
|
+
return response_class(**ok_result)
|
76
|
+
case Error(code, message, data):
|
77
|
+
raise RPCError(code=code, message=message, data=data)
|
78
|
+
case _:
|
79
|
+
raise RuntimeError("how did we get here")
|
80
|
+
|
81
|
+
|
82
|
+
def _retrying_post(url: URL | str, json: object | None = None) -> Response:
|
83
|
+
max_retries = 3
|
84
|
+
total_timeout = 180
|
85
|
+
|
86
|
+
@retry(
|
87
|
+
wait=wait_exponential_jitter(),
|
88
|
+
stop=(stop_after_attempt(max_retries) | stop_after_delay(total_timeout)),
|
89
|
+
retry=retry_if_exception(httpx_should_retry),
|
90
|
+
)
|
91
|
+
def do_post() -> Response:
|
92
|
+
with Client() as client:
|
93
|
+
return client.post(url, json=json, timeout=30)
|
94
|
+
|
95
|
+
return do_post()
|
96
|
+
|
97
|
+
|
98
|
+
RPCArgsType = Type[Mapping[str, object]]
|
99
|
+
|
100
|
+
|
101
|
+
class RPCCallTypes(TypedDict, Generic[TBaseModel]):
|
102
|
+
args_type: RPCArgsType
|
103
|
+
response_class: Type[TBaseModel]
|
104
|
+
|
105
|
+
|
106
|
+
# TODO: cloned from inspect_ai repo code that is unavailable in the container
|
107
|
+
# fix this by copying that source file into the container
|
108
|
+
def httpx_should_retry(ex: BaseException) -> bool:
|
109
|
+
"""Check whether an exception raised from httpx should be retried.
|
110
|
+
|
111
|
+
Implements the strategy described here: https://cloud.google.com/storage/docs/retry-strategy
|
112
|
+
|
113
|
+
Args:
|
114
|
+
ex (BaseException): Exception to examine for retry behavior
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
True if a retry should occur
|
118
|
+
"""
|
119
|
+
# httpx status exception
|
120
|
+
if isinstance(ex, HTTPStatusError):
|
121
|
+
# request timeout
|
122
|
+
if ex.response.status_code == 408:
|
123
|
+
return True
|
124
|
+
# lock timeout
|
125
|
+
elif ex.response.status_code == 409:
|
126
|
+
return True
|
127
|
+
# rate limit
|
128
|
+
elif ex.response.status_code == 429:
|
129
|
+
return True
|
130
|
+
# internal errors
|
131
|
+
elif ex.response.status_code >= 500:
|
132
|
+
return True
|
133
|
+
else:
|
134
|
+
return False
|
135
|
+
|
136
|
+
# connection error
|
137
|
+
elif is_httpx_connection_error(ex):
|
138
|
+
return True
|
139
|
+
|
140
|
+
# don't retry
|
141
|
+
else:
|
142
|
+
return False
|
143
|
+
|
144
|
+
|
145
|
+
def is_httpx_connection_error(ex: BaseException) -> bool:
|
146
|
+
return isinstance(ex, ConnectTimeout | ConnectError | ConnectionError | ReadTimeout)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
|
4
|
+
# Playwright launches Chromium with --force-device-scale-factor=1 by default, which
|
5
|
+
# ensures consistent rendering and measurement behavior using CSS pixels instead of
|
6
|
+
# native device pixels, regardless of the actual display's DPI. On HiDPI displays,
|
7
|
+
# like Apple Retina, the ratio of native pixels to CSS pixels is typically 2x or even 3x.
|
8
|
+
#
|
9
|
+
# However, the Chrome DevTools Protocol (CDP) has an inconsistency when running on
|
10
|
+
# HiDPI devices with --force-device-scale-factor=1. Although window.devicePixelRatio
|
11
|
+
# correctly reports as 1 (honoring the forced scale factor), CDP's LayoutTreeSnapshot.bounds
|
12
|
+
# still returns coordinates in native device pixels without applying the forced scale factor,
|
13
|
+
# resulting in a mismatch between CSS and native pixels.
|
14
|
+
#
|
15
|
+
# To work around this inconsistency, we determine the native scale factor independently
|
16
|
+
# of browser-reported values, using system-level data via the get_screen_scale_factor helper
|
17
|
+
# function. We then apply this actual scale factor to adjust the window bounds, ensuring
|
18
|
+
# they are in the same coordinate space as LayoutTreeSnapshot.bounds. This alignment
|
19
|
+
# allows the code to accurately calculate node visibility.
|
20
|
+
|
21
|
+
|
22
|
+
def get_screen_scale_factor() -> float:
|
23
|
+
if sys.platform == "darwin":
|
24
|
+
from AppKit import NSScreen
|
25
|
+
|
26
|
+
return NSScreen.mainScreen().backingScaleFactor()
|
27
|
+
elif sys.platform == "win32":
|
28
|
+
try:
|
29
|
+
# Using GetDpiForSystem from Windows API
|
30
|
+
import ctypes
|
31
|
+
|
32
|
+
user32 = ctypes.windll.user32
|
33
|
+
user32.SetProcessDPIAware()
|
34
|
+
return user32.GetDpiForSystem() / 96.0
|
35
|
+
except Exception:
|
36
|
+
return 1.0
|
37
|
+
elif sys.platform.startswith("linux"):
|
38
|
+
try:
|
39
|
+
# Try to get scaling from gsettings (GNOME)
|
40
|
+
result = subprocess.run(
|
41
|
+
["gsettings", "get", "org.gnome.desktop.interface", "scaling-factor"],
|
42
|
+
capture_output=True,
|
43
|
+
text=True,
|
44
|
+
)
|
45
|
+
if result.returncode == 0:
|
46
|
+
return float(result.stdout.strip())
|
47
|
+
|
48
|
+
# Try to get scaling from xrandr (X11)
|
49
|
+
result = subprocess.run(
|
50
|
+
["xrandr", "--current"], capture_output=True, text=True
|
51
|
+
)
|
52
|
+
if result.returncode == 0:
|
53
|
+
# Parse xrandr output to find scaling
|
54
|
+
# This is a simplified check - might need adjustment
|
55
|
+
for line in result.stdout.splitlines():
|
56
|
+
if "connected primary" in line and "x" in line:
|
57
|
+
# Look for something like "3840x2160+0+0"
|
58
|
+
resolution = line.split()[3].split("+")[0]
|
59
|
+
if "3840x2160" in resolution: # 4K
|
60
|
+
return 2.0
|
61
|
+
return 1.0
|
62
|
+
except Exception:
|
63
|
+
return 1.0
|
64
|
+
return 1.0 # Default fallback
|
@@ -0,0 +1,180 @@
|
|
1
|
+
from cdp.dom_snapshot import (
|
2
|
+
DocumentSnapshot,
|
3
|
+
DOMSnapshot,
|
4
|
+
LayoutTreeSnapshot,
|
5
|
+
NodeTreeSnapshot,
|
6
|
+
RareBooleanData,
|
7
|
+
StringIndex,
|
8
|
+
TextBoxSnapshot,
|
9
|
+
)
|
10
|
+
|
11
|
+
simple_node_tree = NodeTreeSnapshot()
|
12
|
+
simple_layout_tree = LayoutTreeSnapshot(
|
13
|
+
nodeIndex=(),
|
14
|
+
styles=(),
|
15
|
+
bounds=(),
|
16
|
+
text=(),
|
17
|
+
stackingContexts=RareBooleanData(index=()),
|
18
|
+
)
|
19
|
+
simple_text_boxes = TextBoxSnapshot(layoutIndex=(), bounds=(), start=(), length=())
|
20
|
+
simple_doc_snapshot = DocumentSnapshot(
|
21
|
+
documentURL=StringIndex(0),
|
22
|
+
title=StringIndex(1),
|
23
|
+
baseURL=StringIndex(0),
|
24
|
+
contentLanguage=StringIndex(2),
|
25
|
+
encodingName=StringIndex(3),
|
26
|
+
publicId=StringIndex(666),
|
27
|
+
systemId=StringIndex(666),
|
28
|
+
frameId=StringIndex(666),
|
29
|
+
nodes=simple_node_tree,
|
30
|
+
layout=simple_layout_tree,
|
31
|
+
textBoxes=simple_text_boxes,
|
32
|
+
)
|
33
|
+
simple_dom_snapshot = DOMSnapshot(
|
34
|
+
documents=(), strings=("documentUrl", "title", "contentLanguage", "encoding")
|
35
|
+
)
|
36
|
+
|
37
|
+
# THIS TEST MODULE IS DISABLED FOR NOW. IT WILL COME BACK
|
38
|
+
|
39
|
+
# def test_foo():
|
40
|
+
# ax_node = cast(
|
41
|
+
# AXNode,
|
42
|
+
# {"nodeId": "666", "name": {"value": "Test"}, "role": {"value": "button"}},
|
43
|
+
# )
|
44
|
+
# ax_nodes = {ax_node.nodeId: ax_node}
|
45
|
+
# nodes: dict[AXNodeId, AccessibilityTreeNode] = {}
|
46
|
+
# snapshot_context = create_snapshot_context(simple_dom_snapshot)
|
47
|
+
# window_bounds = Rectangle(0, 0, 1024, 768)
|
48
|
+
|
49
|
+
# node = AccessibilityTreeNode(
|
50
|
+
# ax_node={"name": {"value": "Test"}, "role": {"value": "button"}},
|
51
|
+
# ax_nodes=ax_nodes,
|
52
|
+
# parent=None,
|
53
|
+
# all_accessibility_tree_nodes=nodes,
|
54
|
+
# snapshot_context=snapshot_context,
|
55
|
+
# device_scale_factor=1,
|
56
|
+
# window_bounds=window_bounds,
|
57
|
+
# )
|
58
|
+
|
59
|
+
|
60
|
+
# class TestAccessibilityTreeNode(absltest.TestCase):
|
61
|
+
# def test_getitem(self):
|
62
|
+
# node_data = {"name": {"value": "Test"}, "role": {"value": "button"}}
|
63
|
+
# node = AccessibilityTreeNode(node_data)
|
64
|
+
# self.assertEqual(node["name"], {"value": "Test"})
|
65
|
+
# self.assertEqual(node["role"], {"value": "button"})
|
66
|
+
# self.assertIsNone(node["invalid_key"])
|
67
|
+
|
68
|
+
# def test_setitem(self):
|
69
|
+
# node_data = {"name": {"value": "Test"}}
|
70
|
+
# node = AccessibilityTreeNode(node_data)
|
71
|
+
# node["role"] = {"value": "button"}
|
72
|
+
# self.assertEqual(
|
73
|
+
# node._node, {"name": {"value": "Test"}, "role": {"value": "button"}}
|
74
|
+
# )
|
75
|
+
|
76
|
+
# def test_str_role_name(self):
|
77
|
+
# node_data = {
|
78
|
+
# "nodeId": "1",
|
79
|
+
# "role": {"value": "button"},
|
80
|
+
# "name": {"value": "Test Button"},
|
81
|
+
# }
|
82
|
+
# node = AccessibilityTreeNode(node_data, bounds=Rectangle(10, 10, 20, 20))
|
83
|
+
# self.assertIn('[1] button "Test Button"', str(node))
|
84
|
+
|
85
|
+
# def test_str_image_src(self):
|
86
|
+
# node_data = {
|
87
|
+
# "nodeId": "1",
|
88
|
+
# "role": {"value": "image"},
|
89
|
+
# "name": {"value": "Test Image"},
|
90
|
+
# "src": "image.png",
|
91
|
+
# }
|
92
|
+
# node = AccessibilityTreeNode(node_data, bounds=Rectangle(10, 10, 20, 20))
|
93
|
+
# self.assertIn('[1] image "Test Image" image.png', str(node))
|
94
|
+
|
95
|
+
# def test_str_property(self):
|
96
|
+
# node_data = {
|
97
|
+
# "nodeId": "1",
|
98
|
+
# "role": {"value": "link"},
|
99
|
+
# "name": {"value": "Test Link"},
|
100
|
+
# "properties": [{"name": "url", "value": {"value": "www.example.com"}}],
|
101
|
+
# }
|
102
|
+
# node = AccessibilityTreeNode(node_data, bounds=Rectangle(10, 10, 20, 20))
|
103
|
+
# self.assertIn('[1] link "Test Link" [url: www.example.com]', str(node))
|
104
|
+
|
105
|
+
# def test_str_empty(self):
|
106
|
+
# node_data = {"nodeId": "1"}
|
107
|
+
# node = AccessibilityTreeNode(node_data)
|
108
|
+
# self.assertIn('[*] ""', str(node))
|
109
|
+
|
110
|
+
# def test_link_children(self):
|
111
|
+
# node_data = {"nodeId": "1", "childIds": [2, 3]}
|
112
|
+
# node = AccessibilityTreeNode(node_data)
|
113
|
+
# child1 = AccessibilityTreeNode({"nodeId": "2"})
|
114
|
+
# child2 = AccessibilityTreeNode({"nodeId": "3"})
|
115
|
+
# node_lookup = {2: child1, 3: child2}
|
116
|
+
# node.link_children(node_lookup)
|
117
|
+
|
118
|
+
# self.assertEqual(node.children, [child1, child2])
|
119
|
+
# self.assertEqual(child1._parent, node)
|
120
|
+
# self.assertEqual(child2._parent, node)
|
121
|
+
|
122
|
+
# def test_get_property(self):
|
123
|
+
# node_data = {"name": {"value": "Test"}, "role": {"value": "button"}}
|
124
|
+
# node = AccessibilityTreeNode(node_data)
|
125
|
+
# self.assertEqual(node.get_property("name"), {"value": "Test"})
|
126
|
+
# self.assertEqual(node.get_property("role"), {"value": "button"})
|
127
|
+
# self.assertIsNone(node.get_property("invalid_key"))
|
128
|
+
|
129
|
+
# def test_render(self):
|
130
|
+
# node_data = {
|
131
|
+
# "nodeId": "1",
|
132
|
+
# "ignored": False,
|
133
|
+
# "childIds": [2],
|
134
|
+
# "role": {"value": "button"},
|
135
|
+
# "name": {"value": "Test Button"},
|
136
|
+
# }
|
137
|
+
# node = AccessibilityTreeNode(node_data, bounds=Rectangle(20, 20, 30, 30))
|
138
|
+
# self.assertIn('[1] button "Test Button"', node.render())
|
139
|
+
|
140
|
+
# def test_to_render_with_children(self):
|
141
|
+
# node_data = {
|
142
|
+
# "nodeId": "1",
|
143
|
+
# "ignored": False,
|
144
|
+
# "childIds": [2, 3],
|
145
|
+
# "role": {"value": "button"},
|
146
|
+
# "name": {"value": "Test Button"},
|
147
|
+
# }
|
148
|
+
# node = AccessibilityTreeNode(node_data, bounds=Rectangle(20, 20, 30, 30))
|
149
|
+
# child_data = {
|
150
|
+
# "nodeId": "2",
|
151
|
+
# "ignored": False,
|
152
|
+
# "role": {"value": "text"},
|
153
|
+
# "name": {"value": "Child Text"},
|
154
|
+
# }
|
155
|
+
# child = AccessibilityTreeNode(child_data, bounds=Rectangle(20, 20, 30, 30))
|
156
|
+
# ignored_child_data = {
|
157
|
+
# "nodeId": "3",
|
158
|
+
# "ignored": True,
|
159
|
+
# "role": {"value": "text"},
|
160
|
+
# "name": {"value": "Child Text"},
|
161
|
+
# }
|
162
|
+
# ignored_child = AccessibilityTreeNode(
|
163
|
+
# ignored_child_data, bounds=Rectangle(20, 20, 30, 30)
|
164
|
+
# )
|
165
|
+
# node.link_children({2: child, 3: ignored_child})
|
166
|
+
# self.assertIn('[2] text "Child Text"', node.render())
|
167
|
+
# self.assertNotIn('[3] text "Child Text"', node.render())
|
168
|
+
|
169
|
+
# def test_property_string(self):
|
170
|
+
# node_data = {"properties": [{"name": "checked", "value": {"value": True}}]}
|
171
|
+
# node = AccessibilityTreeNode(node_data)
|
172
|
+
# self.assertEqual(node.property_string(), " [checked: True]")
|
173
|
+
|
174
|
+
# def test_property_string_with_ignored_property(self):
|
175
|
+
# node_data = {
|
176
|
+
# "properties": [{"name": "focusable", "value": {"value": "some_value"}}]
|
177
|
+
# }
|
178
|
+
# node = AccessibilityTreeNode(node_data)
|
179
|
+
# # 'focusable' is in _IGNORED_ACTREE_PROPERTIES
|
180
|
+
# self.assertEqual(node.property_string(), "")
|
@@ -1,10 +1,12 @@
|
|
1
|
-
import playwright_crawler
|
2
1
|
from absl.testing import parameterized
|
3
2
|
|
3
|
+
import playwright_browser
|
4
|
+
import playwright_crawler
|
5
|
+
|
4
6
|
|
5
7
|
class TestPlaywrightCrawler(parameterized.TestCase):
|
6
8
|
def setUp(self):
|
7
|
-
self._browser =
|
9
|
+
self._browser = playwright_browser.PlaywrightBrowser()
|
8
10
|
self._crawler = playwright_crawler.PlaywrightCrawler(
|
9
11
|
self._browser.get_new_context()
|
10
12
|
)
|
@@ -26,12 +28,14 @@ class TestPlaywrightCrawler(parameterized.TestCase):
|
|
26
28
|
|
27
29
|
def test_render_accessibility_tree(self):
|
28
30
|
self._crawler.go_to_page("https://www.example.com")
|
29
|
-
at_no_update = self._crawler.
|
30
|
-
|
31
|
+
at_no_update = self._crawler.render_at(
|
32
|
+
playwright_crawler.CrawlerOutputFormat.AT
|
33
|
+
)
|
34
|
+
self.assertEqual(at_no_update, "")
|
31
35
|
|
32
36
|
self._crawler.update()
|
33
37
|
|
34
|
-
at_update = self._crawler.
|
38
|
+
at_update = self._crawler.render_at(playwright_crawler.CrawlerOutputFormat.AT)
|
35
39
|
nodes = at_update.splitlines()
|
36
40
|
self.assertEqual(len(nodes), 3)
|
37
41
|
self.assertTrue(
|
@@ -72,9 +76,9 @@ class TestPlaywrightCrawler(parameterized.TestCase):
|
|
72
76
|
</body>
|
73
77
|
</html>
|
74
78
|
"""
|
75
|
-
self._crawler.
|
79
|
+
self._crawler._page_future.set_content(test_html)
|
76
80
|
self._crawler.update()
|
77
|
-
at_before_scroll = self._crawler.
|
81
|
+
at_before_scroll = self._crawler.render_at(
|
78
82
|
playwright_crawler.CrawlerOutputFormat.AT
|
79
83
|
)
|
80
84
|
self.assertIn("Scrolling Test Page", at_before_scroll)
|
@@ -82,12 +86,14 @@ class TestPlaywrightCrawler(parameterized.TestCase):
|
|
82
86
|
|
83
87
|
self._crawler.scroll("down")
|
84
88
|
self._crawler.update()
|
85
|
-
at_after_scroll = self._crawler.
|
89
|
+
at_after_scroll = self._crawler.render_at(
|
86
90
|
playwright_crawler.CrawlerOutputFormat.AT
|
87
91
|
)
|
88
92
|
self.assertIn("Click Me", at_after_scroll)
|
89
93
|
|
90
94
|
self._crawler.click("17")
|
91
95
|
self._crawler.update()
|
92
|
-
at_after_click = self._crawler.
|
96
|
+
at_after_click = self._crawler.render_at(
|
97
|
+
playwright_crawler.CrawlerOutputFormat.AT
|
98
|
+
)
|
93
99
|
self.assertIn("Text Changed!", at_after_click)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from rectangle import Rectangle
|
2
|
+
|
3
|
+
|
4
|
+
def test_overlaps():
|
5
|
+
bounds1 = Rectangle(10, 20, 30, 40)
|
6
|
+
bounds2 = Rectangle(20, 30, 40, 50)
|
7
|
+
assert bounds1.overlaps(bounds2)
|
8
|
+
bounds3 = Rectangle(50, 60, 20, 30)
|
9
|
+
assert not bounds1.overlaps(bounds3)
|
10
|
+
|
11
|
+
|
12
|
+
def test_within():
|
13
|
+
bounds1 = Rectangle(231, 167, 2, 9)
|
14
|
+
bounds2 = Rectangle(231, 167, 26, 9)
|
15
|
+
assert bounds1.within(bounds2)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
from web_browser_rpc_types import GoArgs
|
4
|
+
from web_client import _parse_args
|
5
|
+
|
6
|
+
|
7
|
+
def test_parse_args_session_name_handling() -> None:
|
8
|
+
test_cases: list[tuple[list[str], str, object]] = [
|
9
|
+
(
|
10
|
+
["cli", "--session_name=my_session", "web_go", "boston.com"],
|
11
|
+
"web_go",
|
12
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
13
|
+
),
|
14
|
+
(
|
15
|
+
["cli", "web_go", "boston.com", "--session_name=my_session"],
|
16
|
+
"web_go",
|
17
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
18
|
+
),
|
19
|
+
(
|
20
|
+
["cli", "web_go", "--session_name=my_session", "boston.com"],
|
21
|
+
"web_go",
|
22
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
23
|
+
),
|
24
|
+
(
|
25
|
+
["cli", "--session_name", "my_session", "web_go", "boston.com"],
|
26
|
+
"web_go",
|
27
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
28
|
+
),
|
29
|
+
(
|
30
|
+
["cli", "web_go", "boston.com", "--session_name", "my_session"],
|
31
|
+
"web_go",
|
32
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
33
|
+
),
|
34
|
+
(
|
35
|
+
["cli", "web_go", "--session_name", "my_session", "boston.com"],
|
36
|
+
"web_go",
|
37
|
+
GoArgs(session_name="my_session", url="boston.com"),
|
38
|
+
),
|
39
|
+
]
|
40
|
+
for argv, expected_cmd, expected_params in test_cases:
|
41
|
+
sys.argv = argv
|
42
|
+
cmd, params = _parse_args()
|
43
|
+
assert cmd == expected_cmd
|
44
|
+
assert params == expected_params
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from typing import Literal, TypedDict
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class NewSessionArgs(TypedDict):
|
7
|
+
headful: bool
|
8
|
+
|
9
|
+
|
10
|
+
class CrawlerBaseArgs(TypedDict):
|
11
|
+
session_name: str
|
12
|
+
|
13
|
+
|
14
|
+
class GoArgs(CrawlerBaseArgs):
|
15
|
+
url: str
|
16
|
+
|
17
|
+
|
18
|
+
class ClickArgs(CrawlerBaseArgs):
|
19
|
+
element_id: str
|
20
|
+
|
21
|
+
|
22
|
+
class ScrollArgs(CrawlerBaseArgs):
|
23
|
+
direction: Literal["up", "down"]
|
24
|
+
|
25
|
+
|
26
|
+
class TypeOrSubmitArgs(CrawlerBaseArgs):
|
27
|
+
element_id: str
|
28
|
+
text: str
|
29
|
+
|
30
|
+
|
31
|
+
class NewSessionResponse(BaseModel):
|
32
|
+
session_name: str
|
33
|
+
|
34
|
+
|
35
|
+
class CrawlerResponse(BaseModel):
|
36
|
+
web_url: str
|
37
|
+
main_content: str | None = None
|
38
|
+
web_at: str
|
39
|
+
error: str | None = None
|