inspect-ai 0.3.75__py3-none-any.whl → 0.3.76__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/_eval/evalset.py +3 -2
- inspect_ai/_eval/registry.py +3 -5
- inspect_ai/_eval/run.py +4 -0
- inspect_ai/_eval/task/run.py +4 -0
- inspect_ai/_util/logger.py +3 -0
- inspect_ai/_view/www/dist/assets/index.css +28 -16
- inspect_ai/_view/www/dist/assets/index.js +4801 -4615
- inspect_ai/_view/www/log-schema.json +79 -9
- inspect_ai/_view/www/src/samples/descriptor/score/CategoricalScoreDescriptor.tsx +1 -1
- inspect_ai/_view/www/src/samples/descriptor/score/NumericScoreDescriptor.tsx +2 -2
- inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +1 -1
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +2 -2
- inspect_ai/_view/www/src/types/log.d.ts +11 -5
- inspect_ai/log/_recorders/json.py +8 -0
- inspect_ai/log/_transcript.py +13 -4
- inspect_ai/model/_call_tools.py +13 -4
- inspect_ai/model/_chat_message.py +3 -0
- inspect_ai/model/_model.py +5 -1
- inspect_ai/model/_model_output.py +6 -1
- inspect_ai/model/_openai.py +11 -6
- inspect_ai/model/_providers/anthropic.py +133 -75
- inspect_ai/model/_providers/openai.py +11 -8
- inspect_ai/model/_providers/vertex.py +5 -2
- inspect_ai/tool/__init__.py +4 -0
- inspect_ai/tool/_tool_call.py +5 -2
- inspect_ai/tool/_tool_support_helpers.py +200 -0
- inspect_ai/tool/_tools/_bash_session.py +119 -0
- inspect_ai/tool/_tools/_computer/_computer.py +1 -1
- inspect_ai/tool/_tools/_text_editor.py +121 -0
- inspect_ai/tool/_tools/_web_browser/_back_compat.py +150 -0
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +75 -130
- inspect_ai/tool/_tools/_web_search.py +1 -1
- inspect_ai/util/_json.py +28 -0
- inspect_ai/util/_sandbox/context.py +16 -7
- inspect_ai/util/_sandbox/docker/config.py +1 -1
- inspect_ai/util/_sandbox/docker/internal.py +3 -3
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info}/METADATA +5 -2
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info}/RECORD +42 -68
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info}/WHEEL +1 -1
- inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +0 -8
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +0 -24
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +0 -25
- inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +0 -22
- inspect_ai/tool/_tools/_web_browser/_resources/README.md +0 -63
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +0 -71
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +0 -323
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +0 -5
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +0 -279
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +0 -9
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +0 -293
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +0 -94
- inspect_ai/tool/_tools/_web_browser/_resources/constants.py +0 -2
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +0 -2
- inspect_ai/tool/_tools/_web_browser/_resources/mock_environment.py +0 -45
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +0 -50
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +0 -48
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +0 -280
- inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +0 -65
- inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +0 -64
- inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +0 -146
- inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +0 -64
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +0 -180
- inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +0 -99
- inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +0 -15
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +0 -44
- inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +0 -39
- inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +0 -214
- inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +0 -35
- inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +0 -192
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info/licenses}/LICENSE +0 -0
- {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.76.dist-info}/top_level.txt +0 -0
@@ -1,180 +0,0 @@
|
|
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,99 +0,0 @@
|
|
1
|
-
from absl.testing import parameterized
|
2
|
-
|
3
|
-
import playwright_browser
|
4
|
-
import playwright_crawler
|
5
|
-
|
6
|
-
|
7
|
-
class TestPlaywrightCrawler(parameterized.TestCase):
|
8
|
-
def setUp(self):
|
9
|
-
self._browser = playwright_browser.PlaywrightBrowser()
|
10
|
-
self._crawler = playwright_crawler.PlaywrightCrawler(
|
11
|
-
self._browser.get_new_context()
|
12
|
-
)
|
13
|
-
|
14
|
-
def test_go_to_page_changes_url(self):
|
15
|
-
self.assertEqual(self._crawler.url, "about:blank")
|
16
|
-
self._crawler.go_to_page("https://www.example.com")
|
17
|
-
self.assertEqual(self._crawler.url, "https://www.example.com/")
|
18
|
-
|
19
|
-
def test_go_to_page_adds_missing_protocol(self):
|
20
|
-
self._crawler.go_to_page("www.example.com")
|
21
|
-
self.assertEqual(self._crawler.url, "https://www.example.com/")
|
22
|
-
|
23
|
-
def test_nodes_change_on_update(self):
|
24
|
-
self._crawler.go_to_page("https://www.example.com")
|
25
|
-
self.assertFalse(self._crawler._nodes)
|
26
|
-
self._crawler.update()
|
27
|
-
self.assertTrue(self._crawler._nodes)
|
28
|
-
|
29
|
-
def test_render_accessibility_tree(self):
|
30
|
-
self._crawler.go_to_page("https://www.example.com")
|
31
|
-
at_no_update = self._crawler.render_at(
|
32
|
-
playwright_crawler.CrawlerOutputFormat.AT
|
33
|
-
)
|
34
|
-
self.assertEqual(at_no_update, "")
|
35
|
-
|
36
|
-
self._crawler.update()
|
37
|
-
|
38
|
-
at_update = self._crawler.render_at(playwright_crawler.CrawlerOutputFormat.AT)
|
39
|
-
nodes = at_update.splitlines()
|
40
|
-
self.assertEqual(len(nodes), 3)
|
41
|
-
self.assertTrue(
|
42
|
-
nodes[0].startswith(
|
43
|
-
'[5] RootWebArea "Example Domain" [focused: True, url: https://www.example.com/]'
|
44
|
-
)
|
45
|
-
)
|
46
|
-
self.assertTrue(
|
47
|
-
nodes[1].startswith(
|
48
|
-
' [3] StaticText "This domain is for use in illustrative examples in documents'
|
49
|
-
)
|
50
|
-
)
|
51
|
-
self.assertTrue(
|
52
|
-
nodes[2].startswith(
|
53
|
-
' [12] link "More information..." [url: https://www.iana.org/domains/example]'
|
54
|
-
)
|
55
|
-
)
|
56
|
-
|
57
|
-
def test_click_adjusts_to_scrolling_position(self):
|
58
|
-
test_html = """
|
59
|
-
<!DOCTYPE html>
|
60
|
-
<html lang="en">
|
61
|
-
<head>
|
62
|
-
<meta charset="UTF-8">
|
63
|
-
<title>Scrolling Test Page</title>
|
64
|
-
<style>
|
65
|
-
body { height: 3000px; }
|
66
|
-
.my-button { position: absolute; top: 1500px; }
|
67
|
-
</style>
|
68
|
-
</head>
|
69
|
-
<body>
|
70
|
-
<button class="my-button" onclick="changeText(this)">Click Me</button>
|
71
|
-
<script>
|
72
|
-
function changeText(button) {
|
73
|
-
button.textContent = "Text Changed!";
|
74
|
-
}
|
75
|
-
</script>
|
76
|
-
</body>
|
77
|
-
</html>
|
78
|
-
"""
|
79
|
-
self._crawler._page_future.set_content(test_html)
|
80
|
-
self._crawler.update()
|
81
|
-
at_before_scroll = self._crawler.render_at(
|
82
|
-
playwright_crawler.CrawlerOutputFormat.AT
|
83
|
-
)
|
84
|
-
self.assertIn("Scrolling Test Page", at_before_scroll)
|
85
|
-
self.assertNotIn("Click Me", at_before_scroll)
|
86
|
-
|
87
|
-
self._crawler.scroll("down")
|
88
|
-
self._crawler.update()
|
89
|
-
at_after_scroll = self._crawler.render_at(
|
90
|
-
playwright_crawler.CrawlerOutputFormat.AT
|
91
|
-
)
|
92
|
-
self.assertIn("Click Me", at_after_scroll)
|
93
|
-
|
94
|
-
self._crawler.click("17")
|
95
|
-
self._crawler.update()
|
96
|
-
at_after_click = self._crawler.render_at(
|
97
|
-
playwright_crawler.CrawlerOutputFormat.AT
|
98
|
-
)
|
99
|
-
self.assertIn("Text Changed!", at_after_click)
|
@@ -1,15 +0,0 @@
|
|
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)
|
@@ -1,44 +0,0 @@
|
|
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
|
@@ -1,39 +0,0 @@
|
|
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
|
@@ -1,214 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
import sys
|
3
|
-
from typing import Literal
|
4
|
-
|
5
|
-
from constants import DEFAULT_SESSION_NAME, SERVER_PORT
|
6
|
-
from rpc_client_helpers import RPCError, rpc_call
|
7
|
-
from web_browser_rpc_types import (
|
8
|
-
ClickArgs,
|
9
|
-
CrawlerBaseArgs,
|
10
|
-
CrawlerResponse,
|
11
|
-
GoArgs,
|
12
|
-
NewSessionArgs,
|
13
|
-
NewSessionResponse,
|
14
|
-
ScrollArgs,
|
15
|
-
TypeOrSubmitArgs,
|
16
|
-
)
|
17
|
-
|
18
|
-
_SERVER_URL = f"http://localhost:{SERVER_PORT}/"
|
19
|
-
|
20
|
-
|
21
|
-
def main() -> None:
|
22
|
-
if len(sys.argv) > 1:
|
23
|
-
command, params = _parse_args()
|
24
|
-
_execute_command(command, params)
|
25
|
-
else:
|
26
|
-
_interactive_mode()
|
27
|
-
|
28
|
-
|
29
|
-
def _execute_command(
|
30
|
-
command: str,
|
31
|
-
params: NewSessionArgs
|
32
|
-
| GoArgs
|
33
|
-
| ClickArgs
|
34
|
-
| TypeOrSubmitArgs
|
35
|
-
| ScrollArgs
|
36
|
-
| CrawlerBaseArgs,
|
37
|
-
) -> None:
|
38
|
-
try:
|
39
|
-
if command == "new_session":
|
40
|
-
print(
|
41
|
-
rpc_call(
|
42
|
-
_SERVER_URL, command, dict(params), NewSessionResponse
|
43
|
-
).session_name
|
44
|
-
)
|
45
|
-
else:
|
46
|
-
response = rpc_call(
|
47
|
-
_SERVER_URL,
|
48
|
-
command,
|
49
|
-
dict(params),
|
50
|
-
CrawlerResponse,
|
51
|
-
)
|
52
|
-
for key, value in vars(response).items():
|
53
|
-
if value is not None:
|
54
|
-
print(key, ": ", value)
|
55
|
-
|
56
|
-
except RPCError as rpc_error:
|
57
|
-
_return_error(f"error: {rpc_error}")
|
58
|
-
|
59
|
-
|
60
|
-
def _interactive_mode() -> None:
|
61
|
-
print(
|
62
|
-
"Welcome to the Playwright Crawler interactive mode!\n"
|
63
|
-
"commands:\n"
|
64
|
-
" web_go <URL> - goes to the specified url.\n"
|
65
|
-
" web_click <ELEMENT_ID> - clicks on a given element.\n"
|
66
|
-
" web_scroll <up/down> - scrolls up or down one page.\n"
|
67
|
-
" web_forward - navigates forward a page.\n"
|
68
|
-
" web_back - navigates back a page.\n"
|
69
|
-
" web_refresh - reloads current page (F5).\n"
|
70
|
-
" web_type <ELEMENT_ID> <TEXT> - types the specified text into the input with the specified id.\n"
|
71
|
-
" web_type_submit <ELEMENT_ID> <TEXT> - types the specified text into the input with the specified id and presses ENTER to submit the form."
|
72
|
-
)
|
73
|
-
|
74
|
-
session_created = False
|
75
|
-
while True:
|
76
|
-
try:
|
77
|
-
user_input = input("Enter command: ").strip()
|
78
|
-
if user_input.lower() in {"exit", "quit"}:
|
79
|
-
break
|
80
|
-
args = user_input.split()
|
81
|
-
sys.argv = ["cli"] + args
|
82
|
-
command, params = _parse_args()
|
83
|
-
print(f"command: {command}, params: {params}")
|
84
|
-
if not session_created:
|
85
|
-
_execute_command("new_session", NewSessionArgs(headful=True))
|
86
|
-
session_created = True
|
87
|
-
_execute_command(command, params)
|
88
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
89
|
-
print(f"Error: {e}")
|
90
|
-
|
91
|
-
|
92
|
-
def _return_error(error: str) -> None:
|
93
|
-
print(error, file=sys.stderr)
|
94
|
-
sys.exit(1)
|
95
|
-
|
96
|
-
|
97
|
-
def _create_main_parser() -> argparse.ArgumentParser:
|
98
|
-
parser = argparse.ArgumentParser(prog="web_client")
|
99
|
-
parser.add_argument(
|
100
|
-
"--session_name",
|
101
|
-
type=str,
|
102
|
-
required=False,
|
103
|
-
default=DEFAULT_SESSION_NAME,
|
104
|
-
help="Session name",
|
105
|
-
)
|
106
|
-
return parser
|
107
|
-
|
108
|
-
|
109
|
-
def _create_command_parser() -> argparse.ArgumentParser:
|
110
|
-
result = argparse.ArgumentParser(prog="web_client")
|
111
|
-
|
112
|
-
subparsers = result.add_subparsers(dest="command", required=True)
|
113
|
-
|
114
|
-
go_parser = subparsers.add_parser("web_go")
|
115
|
-
go_parser.add_argument("url", type=str, help="URL to navigate to")
|
116
|
-
|
117
|
-
click_parser = subparsers.add_parser("web_click")
|
118
|
-
click_parser.add_argument("element_id", type=str, help="ID of the element to click")
|
119
|
-
|
120
|
-
scroll_parser = subparsers.add_parser("web_scroll")
|
121
|
-
scroll_parser.add_argument(
|
122
|
-
"direction",
|
123
|
-
type=str,
|
124
|
-
choices=["up", "down"],
|
125
|
-
help="Direction to scroll (up or down)",
|
126
|
-
)
|
127
|
-
subparsers.add_parser("web_forward")
|
128
|
-
subparsers.add_parser("web_back")
|
129
|
-
subparsers.add_parser("web_refresh")
|
130
|
-
|
131
|
-
type_parser = subparsers.add_parser("web_type")
|
132
|
-
type_parser.add_argument(
|
133
|
-
"element_id", type=str, help="ID of the element to type into"
|
134
|
-
)
|
135
|
-
type_parser.add_argument("text", type=str, help="The text to type")
|
136
|
-
|
137
|
-
submit_parser = subparsers.add_parser("web_type_submit")
|
138
|
-
submit_parser.add_argument(
|
139
|
-
"element_id",
|
140
|
-
type=str,
|
141
|
-
help="ID of the element to type into and submit",
|
142
|
-
)
|
143
|
-
submit_parser.add_argument("text", type=str, help="The text to type")
|
144
|
-
|
145
|
-
# Add common argument to all subparsers
|
146
|
-
for name, subparser in subparsers.choices.items():
|
147
|
-
if name != "new_session":
|
148
|
-
subparser.add_argument(
|
149
|
-
"--session_name",
|
150
|
-
type=str,
|
151
|
-
nargs="?",
|
152
|
-
required=False,
|
153
|
-
default=DEFAULT_SESSION_NAME,
|
154
|
-
help="Session name",
|
155
|
-
)
|
156
|
-
|
157
|
-
return result
|
158
|
-
|
159
|
-
|
160
|
-
main_parser = _create_main_parser()
|
161
|
-
command_parser = _create_command_parser()
|
162
|
-
|
163
|
-
|
164
|
-
def _parse_args() -> (
|
165
|
-
tuple[Literal["web_go"], GoArgs]
|
166
|
-
| tuple[Literal["web_click"], ClickArgs]
|
167
|
-
| tuple[Literal["web_type", "web_type_submit"], TypeOrSubmitArgs]
|
168
|
-
| tuple[Literal["web_scroll"], ScrollArgs]
|
169
|
-
| tuple[Literal["web_forward", "web_back", "web_refresh"], CrawlerBaseArgs]
|
170
|
-
):
|
171
|
-
# web_client.py supports a very non-standard command line. It has a required named
|
172
|
-
# parameter, --session_name, before the command.
|
173
|
-
# Unfortunately, because we can't break backwards compatibility, we're stuck
|
174
|
-
# with that. To properly parse it, we'll be forced to have a separate parser
|
175
|
-
# for --session_name and merge the results with the normal command parser.
|
176
|
-
|
177
|
-
main_args, remaining_args = main_parser.parse_known_args()
|
178
|
-
session_name = main_args.session_name or DEFAULT_SESSION_NAME
|
179
|
-
|
180
|
-
command_args = command_parser.parse_args(remaining_args)
|
181
|
-
command_args_dict = vars(command_args)
|
182
|
-
|
183
|
-
match command_args.command:
|
184
|
-
case "web_go":
|
185
|
-
return command_args_dict["command"], GoArgs(
|
186
|
-
url=command_args_dict["url"],
|
187
|
-
session_name=session_name,
|
188
|
-
)
|
189
|
-
case "web_click":
|
190
|
-
return command_args_dict["command"], ClickArgs(
|
191
|
-
element_id=command_args_dict["element_id"],
|
192
|
-
session_name=session_name,
|
193
|
-
)
|
194
|
-
case "web_type" | "web_type_submit":
|
195
|
-
return command_args_dict["command"], TypeOrSubmitArgs(
|
196
|
-
element_id=command_args_dict["element_id"],
|
197
|
-
text=command_args_dict["text"],
|
198
|
-
session_name=session_name,
|
199
|
-
)
|
200
|
-
case "web_scroll":
|
201
|
-
return command_args_dict["command"], ScrollArgs(
|
202
|
-
direction=command_args_dict["direction"],
|
203
|
-
session_name=session_name,
|
204
|
-
)
|
205
|
-
case "web_forward" | "web_back" | "web_refresh":
|
206
|
-
return command_args_dict["command"], CrawlerBaseArgs(
|
207
|
-
session_name=session_name,
|
208
|
-
)
|
209
|
-
case _:
|
210
|
-
raise ValueError("Unexpected command")
|
211
|
-
|
212
|
-
|
213
|
-
if __name__ == "__main__":
|
214
|
-
main()
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
import sys
|
3
|
-
|
4
|
-
from constants import SERVER_PORT
|
5
|
-
from rpc_client_helpers import RPCError, rpc_call
|
6
|
-
from web_browser_rpc_types import NewSessionArgs, NewSessionResponse
|
7
|
-
|
8
|
-
|
9
|
-
def main() -> None:
|
10
|
-
parser = argparse.ArgumentParser(prog="web_client_new_session")
|
11
|
-
parser.add_argument(
|
12
|
-
"--headful", action="store_true", help="Run in headful mode for testing"
|
13
|
-
)
|
14
|
-
args_class = parser.parse_args()
|
15
|
-
args_dict = vars(args_class)
|
16
|
-
# TODO: Frick. this does no validation
|
17
|
-
params_typed_dict = NewSessionArgs(headful=args_dict["headful"])
|
18
|
-
params = dict(params_typed_dict)
|
19
|
-
|
20
|
-
try:
|
21
|
-
print(
|
22
|
-
rpc_call(
|
23
|
-
f"http://localhost:{SERVER_PORT}/",
|
24
|
-
"new_session",
|
25
|
-
params,
|
26
|
-
NewSessionResponse,
|
27
|
-
).session_name
|
28
|
-
)
|
29
|
-
except RPCError as rpc_error:
|
30
|
-
print(rpc_error, file=sys.stderr)
|
31
|
-
sys.exit(1)
|
32
|
-
|
33
|
-
|
34
|
-
if __name__ == "__main__":
|
35
|
-
main()
|