optexity-browser-use 0.9.5__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.
- browser_use/__init__.py +157 -0
- browser_use/actor/__init__.py +11 -0
- browser_use/actor/element.py +1175 -0
- browser_use/actor/mouse.py +134 -0
- browser_use/actor/page.py +561 -0
- browser_use/actor/playground/flights.py +41 -0
- browser_use/actor/playground/mixed_automation.py +54 -0
- browser_use/actor/playground/playground.py +236 -0
- browser_use/actor/utils.py +176 -0
- browser_use/agent/cloud_events.py +282 -0
- browser_use/agent/gif.py +424 -0
- browser_use/agent/judge.py +170 -0
- browser_use/agent/message_manager/service.py +473 -0
- browser_use/agent/message_manager/utils.py +52 -0
- browser_use/agent/message_manager/views.py +98 -0
- browser_use/agent/prompts.py +413 -0
- browser_use/agent/service.py +2316 -0
- browser_use/agent/system_prompt.md +185 -0
- browser_use/agent/system_prompt_flash.md +10 -0
- browser_use/agent/system_prompt_no_thinking.md +183 -0
- browser_use/agent/views.py +743 -0
- browser_use/browser/__init__.py +41 -0
- browser_use/browser/cloud/cloud.py +203 -0
- browser_use/browser/cloud/views.py +89 -0
- browser_use/browser/events.py +578 -0
- browser_use/browser/profile.py +1158 -0
- browser_use/browser/python_highlights.py +548 -0
- browser_use/browser/session.py +3225 -0
- browser_use/browser/session_manager.py +399 -0
- browser_use/browser/video_recorder.py +162 -0
- browser_use/browser/views.py +200 -0
- browser_use/browser/watchdog_base.py +260 -0
- browser_use/browser/watchdogs/__init__.py +0 -0
- browser_use/browser/watchdogs/aboutblank_watchdog.py +253 -0
- browser_use/browser/watchdogs/crash_watchdog.py +335 -0
- browser_use/browser/watchdogs/default_action_watchdog.py +2729 -0
- browser_use/browser/watchdogs/dom_watchdog.py +817 -0
- browser_use/browser/watchdogs/downloads_watchdog.py +1277 -0
- browser_use/browser/watchdogs/local_browser_watchdog.py +461 -0
- browser_use/browser/watchdogs/permissions_watchdog.py +43 -0
- browser_use/browser/watchdogs/popups_watchdog.py +143 -0
- browser_use/browser/watchdogs/recording_watchdog.py +126 -0
- browser_use/browser/watchdogs/screenshot_watchdog.py +62 -0
- browser_use/browser/watchdogs/security_watchdog.py +280 -0
- browser_use/browser/watchdogs/storage_state_watchdog.py +335 -0
- browser_use/cli.py +2359 -0
- browser_use/code_use/__init__.py +16 -0
- browser_use/code_use/formatting.py +192 -0
- browser_use/code_use/namespace.py +665 -0
- browser_use/code_use/notebook_export.py +276 -0
- browser_use/code_use/service.py +1340 -0
- browser_use/code_use/system_prompt.md +574 -0
- browser_use/code_use/utils.py +150 -0
- browser_use/code_use/views.py +171 -0
- browser_use/config.py +505 -0
- browser_use/controller/__init__.py +3 -0
- browser_use/dom/enhanced_snapshot.py +161 -0
- browser_use/dom/markdown_extractor.py +169 -0
- browser_use/dom/playground/extraction.py +312 -0
- browser_use/dom/playground/multi_act.py +32 -0
- browser_use/dom/serializer/clickable_elements.py +200 -0
- browser_use/dom/serializer/code_use_serializer.py +287 -0
- browser_use/dom/serializer/eval_serializer.py +478 -0
- browser_use/dom/serializer/html_serializer.py +212 -0
- browser_use/dom/serializer/paint_order.py +197 -0
- browser_use/dom/serializer/serializer.py +1170 -0
- browser_use/dom/service.py +825 -0
- browser_use/dom/utils.py +129 -0
- browser_use/dom/views.py +906 -0
- browser_use/exceptions.py +5 -0
- browser_use/filesystem/__init__.py +0 -0
- browser_use/filesystem/file_system.py +619 -0
- browser_use/init_cmd.py +376 -0
- browser_use/integrations/gmail/__init__.py +24 -0
- browser_use/integrations/gmail/actions.py +115 -0
- browser_use/integrations/gmail/service.py +225 -0
- browser_use/llm/__init__.py +155 -0
- browser_use/llm/anthropic/chat.py +242 -0
- browser_use/llm/anthropic/serializer.py +312 -0
- browser_use/llm/aws/__init__.py +36 -0
- browser_use/llm/aws/chat_anthropic.py +242 -0
- browser_use/llm/aws/chat_bedrock.py +289 -0
- browser_use/llm/aws/serializer.py +257 -0
- browser_use/llm/azure/chat.py +91 -0
- browser_use/llm/base.py +57 -0
- browser_use/llm/browser_use/__init__.py +3 -0
- browser_use/llm/browser_use/chat.py +201 -0
- browser_use/llm/cerebras/chat.py +193 -0
- browser_use/llm/cerebras/serializer.py +109 -0
- browser_use/llm/deepseek/chat.py +212 -0
- browser_use/llm/deepseek/serializer.py +109 -0
- browser_use/llm/exceptions.py +29 -0
- browser_use/llm/google/__init__.py +3 -0
- browser_use/llm/google/chat.py +542 -0
- browser_use/llm/google/serializer.py +120 -0
- browser_use/llm/groq/chat.py +229 -0
- browser_use/llm/groq/parser.py +158 -0
- browser_use/llm/groq/serializer.py +159 -0
- browser_use/llm/messages.py +238 -0
- browser_use/llm/models.py +271 -0
- browser_use/llm/oci_raw/__init__.py +10 -0
- browser_use/llm/oci_raw/chat.py +443 -0
- browser_use/llm/oci_raw/serializer.py +229 -0
- browser_use/llm/ollama/chat.py +97 -0
- browser_use/llm/ollama/serializer.py +143 -0
- browser_use/llm/openai/chat.py +264 -0
- browser_use/llm/openai/like.py +15 -0
- browser_use/llm/openai/serializer.py +165 -0
- browser_use/llm/openrouter/chat.py +211 -0
- browser_use/llm/openrouter/serializer.py +26 -0
- browser_use/llm/schema.py +176 -0
- browser_use/llm/views.py +48 -0
- browser_use/logging_config.py +330 -0
- browser_use/mcp/__init__.py +18 -0
- browser_use/mcp/__main__.py +12 -0
- browser_use/mcp/client.py +544 -0
- browser_use/mcp/controller.py +264 -0
- browser_use/mcp/server.py +1114 -0
- browser_use/observability.py +204 -0
- browser_use/py.typed +0 -0
- browser_use/sandbox/__init__.py +41 -0
- browser_use/sandbox/sandbox.py +637 -0
- browser_use/sandbox/views.py +132 -0
- browser_use/screenshots/__init__.py +1 -0
- browser_use/screenshots/service.py +52 -0
- browser_use/sync/__init__.py +6 -0
- browser_use/sync/auth.py +357 -0
- browser_use/sync/service.py +161 -0
- browser_use/telemetry/__init__.py +51 -0
- browser_use/telemetry/service.py +112 -0
- browser_use/telemetry/views.py +101 -0
- browser_use/tokens/__init__.py +0 -0
- browser_use/tokens/custom_pricing.py +24 -0
- browser_use/tokens/mappings.py +4 -0
- browser_use/tokens/service.py +580 -0
- browser_use/tokens/views.py +108 -0
- browser_use/tools/registry/service.py +572 -0
- browser_use/tools/registry/views.py +174 -0
- browser_use/tools/service.py +1675 -0
- browser_use/tools/utils.py +82 -0
- browser_use/tools/views.py +100 -0
- browser_use/utils.py +670 -0
- optexity_browser_use-0.9.5.dist-info/METADATA +344 -0
- optexity_browser_use-0.9.5.dist-info/RECORD +147 -0
- optexity_browser_use-0.9.5.dist-info/WHEEL +4 -0
- optexity_browser_use-0.9.5.dist-info/entry_points.txt +3 -0
- optexity_browser_use-0.9.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from browser_use import Agent, Browser, ChatOpenAI
|
|
4
|
+
|
|
5
|
+
llm = ChatOpenAI('gpt-4.1-mini')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def main():
|
|
9
|
+
"""
|
|
10
|
+
Main function demonstrating mixed automation with Browser-Use and Playwright.
|
|
11
|
+
"""
|
|
12
|
+
print('🚀 Mixed Automation with Browser-Use and Actor API')
|
|
13
|
+
|
|
14
|
+
browser = Browser(keep_alive=True)
|
|
15
|
+
await browser.start()
|
|
16
|
+
|
|
17
|
+
page = await browser.get_current_page() or await browser.new_page()
|
|
18
|
+
|
|
19
|
+
# Go to apple wikipedia page
|
|
20
|
+
await page.goto('https://www.google.com/travel/flights')
|
|
21
|
+
|
|
22
|
+
await asyncio.sleep(1)
|
|
23
|
+
|
|
24
|
+
round_trip_button = await page.must_get_element_by_prompt('round trip button', llm)
|
|
25
|
+
await round_trip_button.click()
|
|
26
|
+
|
|
27
|
+
one_way_button = await page.must_get_element_by_prompt('one way button', llm)
|
|
28
|
+
await one_way_button.click()
|
|
29
|
+
|
|
30
|
+
await asyncio.sleep(1)
|
|
31
|
+
|
|
32
|
+
agent = Agent(task='Find the cheapest flight from London to Paris on 2025-10-15', llm=llm, browser_session=browser)
|
|
33
|
+
await agent.run()
|
|
34
|
+
|
|
35
|
+
input('Press Enter to continue...')
|
|
36
|
+
|
|
37
|
+
await browser.stop()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from browser_use import Browser, ChatOpenAI
|
|
6
|
+
|
|
7
|
+
TASK = """
|
|
8
|
+
On the current wikipedia page, find the latest huge edit and tell me what is was about.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LatestEditFinder(BaseModel):
|
|
13
|
+
"""Find the latest huge edit on the current wikipedia page."""
|
|
14
|
+
|
|
15
|
+
latest_edit: str
|
|
16
|
+
edit_time: str
|
|
17
|
+
edit_author: str
|
|
18
|
+
edit_summary: str
|
|
19
|
+
edit_url: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
llm = ChatOpenAI('gpt-4.1-mini')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
"""
|
|
27
|
+
Main function demonstrating mixed automation with Browser-Use and Playwright.
|
|
28
|
+
"""
|
|
29
|
+
print('🚀 Mixed Automation with Browser-Use and Actor API')
|
|
30
|
+
|
|
31
|
+
browser = Browser(keep_alive=True)
|
|
32
|
+
await browser.start()
|
|
33
|
+
|
|
34
|
+
page = await browser.get_current_page() or await browser.new_page()
|
|
35
|
+
|
|
36
|
+
# Go to apple wikipedia page
|
|
37
|
+
await page.goto('https://browser-use.github.io/stress-tests/challenges/angularjs-form.html')
|
|
38
|
+
|
|
39
|
+
await asyncio.sleep(1)
|
|
40
|
+
|
|
41
|
+
element = await page.get_element_by_prompt('zip code input', llm)
|
|
42
|
+
|
|
43
|
+
print('Element found', element)
|
|
44
|
+
|
|
45
|
+
if element:
|
|
46
|
+
await element.click()
|
|
47
|
+
else:
|
|
48
|
+
print('No element found')
|
|
49
|
+
|
|
50
|
+
await browser.stop()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == '__main__':
|
|
54
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Playground script to test the browser-use actor API.
|
|
4
|
+
|
|
5
|
+
This script demonstrates:
|
|
6
|
+
- Starting a browser session
|
|
7
|
+
- Using the actor API to navigate and interact
|
|
8
|
+
- Finding elements, clicking, scrolling, JavaScript evaluation
|
|
9
|
+
- Testing most of the available methods
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from browser_use import Browser
|
|
17
|
+
|
|
18
|
+
# Configure logging to see what's happening
|
|
19
|
+
logging.basicConfig(level=logging.INFO)
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
"""Main playground function."""
|
|
25
|
+
logger.info('🚀 Starting browser actor playground')
|
|
26
|
+
|
|
27
|
+
# Create browser session
|
|
28
|
+
browser = Browser()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# Start the browser
|
|
32
|
+
await browser.start()
|
|
33
|
+
logger.info('✅ Browser session started')
|
|
34
|
+
|
|
35
|
+
# Navigate to Wikipedia using integrated methods
|
|
36
|
+
logger.info('📖 Navigating to Wikipedia...')
|
|
37
|
+
page = await browser.new_page('https://en.wikipedia.org')
|
|
38
|
+
|
|
39
|
+
# Get basic page info
|
|
40
|
+
url = await page.get_url()
|
|
41
|
+
title = await page.get_title()
|
|
42
|
+
logger.info(f'📄 Page loaded: {title} ({url})')
|
|
43
|
+
|
|
44
|
+
# Take a screenshot
|
|
45
|
+
logger.info('📸 Taking initial screenshot...')
|
|
46
|
+
screenshot_b64 = await page.screenshot()
|
|
47
|
+
logger.info(f'📸 Screenshot captured: {len(screenshot_b64)} bytes')
|
|
48
|
+
|
|
49
|
+
# Set viewport size
|
|
50
|
+
logger.info('🖥️ Setting viewport to 1920x1080...')
|
|
51
|
+
await page.set_viewport_size(1920, 1080)
|
|
52
|
+
|
|
53
|
+
# Execute some JavaScript to count links
|
|
54
|
+
logger.info('🔍 Counting article links using JavaScript...')
|
|
55
|
+
js_code = """() => {
|
|
56
|
+
// Find all article links on the page
|
|
57
|
+
const links = Array.from(document.querySelectorAll('a[href*="/wiki/"]:not([href*=":"])'))
|
|
58
|
+
.filter(link => !link.href.includes('Main_Page') && !link.href.includes('Special:'));
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
total: links.length,
|
|
62
|
+
sample: links.slice(0, 3).map(link => ({
|
|
63
|
+
href: link.href,
|
|
64
|
+
text: link.textContent.trim()
|
|
65
|
+
}))
|
|
66
|
+
};
|
|
67
|
+
}"""
|
|
68
|
+
|
|
69
|
+
link_info = json.loads(await page.evaluate(js_code))
|
|
70
|
+
logger.info(f'🔗 Found {link_info["total"]} article links')
|
|
71
|
+
# Try to find and interact with links using CSS selector
|
|
72
|
+
try:
|
|
73
|
+
# Find article links on the page
|
|
74
|
+
links = await page.get_elements_by_css_selector('a[href*="/wiki/"]:not([href*=":"])')
|
|
75
|
+
|
|
76
|
+
if links:
|
|
77
|
+
logger.info(f'📋 Found {len(links)} wiki links via CSS selector')
|
|
78
|
+
|
|
79
|
+
# Pick the first link
|
|
80
|
+
link_element = links[0]
|
|
81
|
+
|
|
82
|
+
# Get link info using available methods
|
|
83
|
+
basic_info = await link_element.get_basic_info()
|
|
84
|
+
link_href = await link_element.get_attribute('href')
|
|
85
|
+
|
|
86
|
+
logger.info(f'🎯 Selected element: <{basic_info["nodeName"]}>')
|
|
87
|
+
logger.info(f'🔗 Link href: {link_href}')
|
|
88
|
+
|
|
89
|
+
if basic_info['boundingBox']:
|
|
90
|
+
bbox = basic_info['boundingBox']
|
|
91
|
+
logger.info(f'📏 Position: ({bbox["x"]}, {bbox["y"]}) Size: {bbox["width"]}x{bbox["height"]}')
|
|
92
|
+
|
|
93
|
+
# Test element interactions with robust implementations
|
|
94
|
+
logger.info('👆 Hovering over the element...')
|
|
95
|
+
await link_element.hover()
|
|
96
|
+
await asyncio.sleep(1)
|
|
97
|
+
|
|
98
|
+
logger.info('🔍 Focusing the element...')
|
|
99
|
+
await link_element.focus()
|
|
100
|
+
await asyncio.sleep(0.5)
|
|
101
|
+
|
|
102
|
+
# Click the link using robust click method
|
|
103
|
+
logger.info('🖱️ Clicking the link with robust fallbacks...')
|
|
104
|
+
await link_element.click()
|
|
105
|
+
|
|
106
|
+
# Wait for navigation
|
|
107
|
+
await asyncio.sleep(3)
|
|
108
|
+
|
|
109
|
+
# Get new page info
|
|
110
|
+
new_url = await page.get_url()
|
|
111
|
+
new_title = await page.get_title()
|
|
112
|
+
logger.info(f'📄 Navigated to: {new_title}')
|
|
113
|
+
logger.info(f'🌐 New URL: {new_url}')
|
|
114
|
+
else:
|
|
115
|
+
logger.warning('❌ No links found to interact with')
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning(f'⚠️ Link interaction failed: {e}')
|
|
119
|
+
|
|
120
|
+
# Scroll down the page
|
|
121
|
+
logger.info('📜 Scrolling down the page...')
|
|
122
|
+
mouse = await page.mouse
|
|
123
|
+
await mouse.scroll(x=0, y=100, delta_y=500)
|
|
124
|
+
await asyncio.sleep(1)
|
|
125
|
+
|
|
126
|
+
# Test mouse operations
|
|
127
|
+
logger.info('🖱️ Testing mouse operations...')
|
|
128
|
+
await mouse.move(x=100, y=200)
|
|
129
|
+
await mouse.click(x=150, y=250)
|
|
130
|
+
|
|
131
|
+
# Execute more JavaScript examples
|
|
132
|
+
logger.info('🧪 Testing JavaScript evaluation...')
|
|
133
|
+
|
|
134
|
+
# Simple expressions
|
|
135
|
+
page_height = await page.evaluate('() => document.body.scrollHeight')
|
|
136
|
+
current_scroll = await page.evaluate('() => window.pageYOffset')
|
|
137
|
+
logger.info(f'📏 Page height: {page_height}px, current scroll: {current_scroll}px')
|
|
138
|
+
|
|
139
|
+
# JavaScript with arguments
|
|
140
|
+
result = await page.evaluate('(x) => x * 2', 21)
|
|
141
|
+
logger.info(f'🧮 JavaScript with args: 21 * 2 = {result}')
|
|
142
|
+
|
|
143
|
+
# More complex JavaScript
|
|
144
|
+
page_stats = json.loads(
|
|
145
|
+
await page.evaluate("""() => {
|
|
146
|
+
return {
|
|
147
|
+
url: window.location.href,
|
|
148
|
+
title: document.title,
|
|
149
|
+
links: document.querySelectorAll('a').length,
|
|
150
|
+
images: document.querySelectorAll('img').length,
|
|
151
|
+
scrollTop: window.pageYOffset,
|
|
152
|
+
viewportHeight: window.innerHeight
|
|
153
|
+
};
|
|
154
|
+
}""")
|
|
155
|
+
)
|
|
156
|
+
logger.info(f'📊 Page stats: {page_stats}')
|
|
157
|
+
|
|
158
|
+
# Get page title using different methods
|
|
159
|
+
title_via_js = await page.evaluate('() => document.title')
|
|
160
|
+
title_via_api = await page.get_title()
|
|
161
|
+
logger.info(f'📝 Title via JS: "{title_via_js}"')
|
|
162
|
+
logger.info(f'📝 Title via API: "{title_via_api}"')
|
|
163
|
+
|
|
164
|
+
# Take a final screenshot
|
|
165
|
+
logger.info('📸 Taking final screenshot...')
|
|
166
|
+
final_screenshot = await page.screenshot()
|
|
167
|
+
logger.info(f'📸 Final screenshot: {len(final_screenshot)} bytes')
|
|
168
|
+
|
|
169
|
+
# Test browser navigation with error handling
|
|
170
|
+
logger.info('⬅️ Testing browser back navigation...')
|
|
171
|
+
try:
|
|
172
|
+
await page.go_back()
|
|
173
|
+
await asyncio.sleep(2)
|
|
174
|
+
|
|
175
|
+
back_url = await page.get_url()
|
|
176
|
+
back_title = await page.get_title()
|
|
177
|
+
logger.info(f'📄 After going back: {back_title}')
|
|
178
|
+
logger.info(f'🌐 Back URL: {back_url}')
|
|
179
|
+
except RuntimeError as e:
|
|
180
|
+
logger.info(f'ℹ️ Navigation back failed as expected: {e}')
|
|
181
|
+
|
|
182
|
+
# Test creating new page
|
|
183
|
+
logger.info('🆕 Creating new blank page...')
|
|
184
|
+
new_page = await browser.new_page()
|
|
185
|
+
new_page_url = await new_page.get_url()
|
|
186
|
+
logger.info(f'🆕 New page created with URL: {new_page_url}')
|
|
187
|
+
|
|
188
|
+
# Get all pages
|
|
189
|
+
all_pages = await browser.get_pages()
|
|
190
|
+
logger.info(f'📑 Total pages: {len(all_pages)}')
|
|
191
|
+
|
|
192
|
+
# Test form interaction if we can find a form
|
|
193
|
+
try:
|
|
194
|
+
# Look for search input on the page
|
|
195
|
+
search_inputs = await page.get_elements_by_css_selector('input[type="search"], input[name*="search"]')
|
|
196
|
+
|
|
197
|
+
if search_inputs:
|
|
198
|
+
search_input = search_inputs[0]
|
|
199
|
+
logger.info('🔍 Found search input, testing form interaction...')
|
|
200
|
+
|
|
201
|
+
await search_input.focus()
|
|
202
|
+
await search_input.fill('test search query')
|
|
203
|
+
await page.press('Enter')
|
|
204
|
+
|
|
205
|
+
logger.info('✅ Form interaction test completed')
|
|
206
|
+
else:
|
|
207
|
+
logger.info('ℹ️ No search inputs found for form testing')
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.info(f'ℹ️ Form interaction test skipped: {e}')
|
|
211
|
+
|
|
212
|
+
# wait 2 seconds before closing the new page
|
|
213
|
+
logger.info('🕒 Waiting 2 seconds before closing the new page...')
|
|
214
|
+
await asyncio.sleep(2)
|
|
215
|
+
logger.info('🗑️ Closing new page...')
|
|
216
|
+
await browser.close_page(new_page)
|
|
217
|
+
|
|
218
|
+
logger.info('✅ Playground completed successfully!')
|
|
219
|
+
|
|
220
|
+
input('Press Enter to continue...')
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.error(f'❌ Error in playground: {e}', exc_info=True)
|
|
224
|
+
|
|
225
|
+
finally:
|
|
226
|
+
# Clean up
|
|
227
|
+
logger.info('🧹 Cleaning up...')
|
|
228
|
+
try:
|
|
229
|
+
await browser.stop()
|
|
230
|
+
logger.info('✅ Browser session stopped')
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f'❌ Error stopping browser: {e}')
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == '__main__':
|
|
236
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Utility functions for actor operations."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Utils:
|
|
5
|
+
"""Utility functions for actor operations."""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def get_key_info(key: str) -> tuple[str, int | None]:
|
|
9
|
+
"""Get the code and windowsVirtualKeyCode for a key.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
key: Key name (e.g., 'Enter', 'ArrowUp', 'a', 'A')
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Tuple of (code, windowsVirtualKeyCode)
|
|
16
|
+
|
|
17
|
+
Reference: Windows Virtual Key Codes
|
|
18
|
+
https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
|
19
|
+
"""
|
|
20
|
+
# Complete mapping of key names to (code, virtualKeyCode)
|
|
21
|
+
# Based on standard Windows Virtual Key Codes
|
|
22
|
+
key_map = {
|
|
23
|
+
# Navigation keys
|
|
24
|
+
'Backspace': ('Backspace', 8),
|
|
25
|
+
'Tab': ('Tab', 9),
|
|
26
|
+
'Enter': ('Enter', 13),
|
|
27
|
+
'Escape': ('Escape', 27),
|
|
28
|
+
'Space': ('Space', 32),
|
|
29
|
+
' ': ('Space', 32),
|
|
30
|
+
'PageUp': ('PageUp', 33),
|
|
31
|
+
'PageDown': ('PageDown', 34),
|
|
32
|
+
'End': ('End', 35),
|
|
33
|
+
'Home': ('Home', 36),
|
|
34
|
+
'ArrowLeft': ('ArrowLeft', 37),
|
|
35
|
+
'ArrowUp': ('ArrowUp', 38),
|
|
36
|
+
'ArrowRight': ('ArrowRight', 39),
|
|
37
|
+
'ArrowDown': ('ArrowDown', 40),
|
|
38
|
+
'Insert': ('Insert', 45),
|
|
39
|
+
'Delete': ('Delete', 46),
|
|
40
|
+
# Modifier keys
|
|
41
|
+
'Shift': ('ShiftLeft', 16),
|
|
42
|
+
'ShiftLeft': ('ShiftLeft', 16),
|
|
43
|
+
'ShiftRight': ('ShiftRight', 16),
|
|
44
|
+
'Control': ('ControlLeft', 17),
|
|
45
|
+
'ControlLeft': ('ControlLeft', 17),
|
|
46
|
+
'ControlRight': ('ControlRight', 17),
|
|
47
|
+
'Alt': ('AltLeft', 18),
|
|
48
|
+
'AltLeft': ('AltLeft', 18),
|
|
49
|
+
'AltRight': ('AltRight', 18),
|
|
50
|
+
'Meta': ('MetaLeft', 91),
|
|
51
|
+
'MetaLeft': ('MetaLeft', 91),
|
|
52
|
+
'MetaRight': ('MetaRight', 92),
|
|
53
|
+
# Function keys F1-F24
|
|
54
|
+
'F1': ('F1', 112),
|
|
55
|
+
'F2': ('F2', 113),
|
|
56
|
+
'F3': ('F3', 114),
|
|
57
|
+
'F4': ('F4', 115),
|
|
58
|
+
'F5': ('F5', 116),
|
|
59
|
+
'F6': ('F6', 117),
|
|
60
|
+
'F7': ('F7', 118),
|
|
61
|
+
'F8': ('F8', 119),
|
|
62
|
+
'F9': ('F9', 120),
|
|
63
|
+
'F10': ('F10', 121),
|
|
64
|
+
'F11': ('F11', 122),
|
|
65
|
+
'F12': ('F12', 123),
|
|
66
|
+
'F13': ('F13', 124),
|
|
67
|
+
'F14': ('F14', 125),
|
|
68
|
+
'F15': ('F15', 126),
|
|
69
|
+
'F16': ('F16', 127),
|
|
70
|
+
'F17': ('F17', 128),
|
|
71
|
+
'F18': ('F18', 129),
|
|
72
|
+
'F19': ('F19', 130),
|
|
73
|
+
'F20': ('F20', 131),
|
|
74
|
+
'F21': ('F21', 132),
|
|
75
|
+
'F22': ('F22', 133),
|
|
76
|
+
'F23': ('F23', 134),
|
|
77
|
+
'F24': ('F24', 135),
|
|
78
|
+
# Numpad keys
|
|
79
|
+
'NumLock': ('NumLock', 144),
|
|
80
|
+
'Numpad0': ('Numpad0', 96),
|
|
81
|
+
'Numpad1': ('Numpad1', 97),
|
|
82
|
+
'Numpad2': ('Numpad2', 98),
|
|
83
|
+
'Numpad3': ('Numpad3', 99),
|
|
84
|
+
'Numpad4': ('Numpad4', 100),
|
|
85
|
+
'Numpad5': ('Numpad5', 101),
|
|
86
|
+
'Numpad6': ('Numpad6', 102),
|
|
87
|
+
'Numpad7': ('Numpad7', 103),
|
|
88
|
+
'Numpad8': ('Numpad8', 104),
|
|
89
|
+
'Numpad9': ('Numpad9', 105),
|
|
90
|
+
'NumpadMultiply': ('NumpadMultiply', 106),
|
|
91
|
+
'NumpadAdd': ('NumpadAdd', 107),
|
|
92
|
+
'NumpadSubtract': ('NumpadSubtract', 109),
|
|
93
|
+
'NumpadDecimal': ('NumpadDecimal', 110),
|
|
94
|
+
'NumpadDivide': ('NumpadDivide', 111),
|
|
95
|
+
# Lock keys
|
|
96
|
+
'CapsLock': ('CapsLock', 20),
|
|
97
|
+
'ScrollLock': ('ScrollLock', 145),
|
|
98
|
+
# OEM/Punctuation keys (US keyboard layout)
|
|
99
|
+
'Semicolon': ('Semicolon', 186),
|
|
100
|
+
';': ('Semicolon', 186),
|
|
101
|
+
'Equal': ('Equal', 187),
|
|
102
|
+
'=': ('Equal', 187),
|
|
103
|
+
'Comma': ('Comma', 188),
|
|
104
|
+
',': ('Comma', 188),
|
|
105
|
+
'Minus': ('Minus', 189),
|
|
106
|
+
'-': ('Minus', 189),
|
|
107
|
+
'Period': ('Period', 190),
|
|
108
|
+
'.': ('Period', 190),
|
|
109
|
+
'Slash': ('Slash', 191),
|
|
110
|
+
'/': ('Slash', 191),
|
|
111
|
+
'Backquote': ('Backquote', 192),
|
|
112
|
+
'`': ('Backquote', 192),
|
|
113
|
+
'BracketLeft': ('BracketLeft', 219),
|
|
114
|
+
'[': ('BracketLeft', 219),
|
|
115
|
+
'Backslash': ('Backslash', 220),
|
|
116
|
+
'\\': ('Backslash', 220),
|
|
117
|
+
'BracketRight': ('BracketRight', 221),
|
|
118
|
+
']': ('BracketRight', 221),
|
|
119
|
+
'Quote': ('Quote', 222),
|
|
120
|
+
"'": ('Quote', 222),
|
|
121
|
+
# Media/Browser keys
|
|
122
|
+
'AudioVolumeMute': ('AudioVolumeMute', 173),
|
|
123
|
+
'AudioVolumeDown': ('AudioVolumeDown', 174),
|
|
124
|
+
'AudioVolumeUp': ('AudioVolumeUp', 175),
|
|
125
|
+
'MediaTrackNext': ('MediaTrackNext', 176),
|
|
126
|
+
'MediaTrackPrevious': ('MediaTrackPrevious', 177),
|
|
127
|
+
'MediaStop': ('MediaStop', 178),
|
|
128
|
+
'MediaPlayPause': ('MediaPlayPause', 179),
|
|
129
|
+
'BrowserBack': ('BrowserBack', 166),
|
|
130
|
+
'BrowserForward': ('BrowserForward', 167),
|
|
131
|
+
'BrowserRefresh': ('BrowserRefresh', 168),
|
|
132
|
+
'BrowserStop': ('BrowserStop', 169),
|
|
133
|
+
'BrowserSearch': ('BrowserSearch', 170),
|
|
134
|
+
'BrowserFavorites': ('BrowserFavorites', 171),
|
|
135
|
+
'BrowserHome': ('BrowserHome', 172),
|
|
136
|
+
# Additional common keys
|
|
137
|
+
'Clear': ('Clear', 12),
|
|
138
|
+
'Pause': ('Pause', 19),
|
|
139
|
+
'Select': ('Select', 41),
|
|
140
|
+
'Print': ('Print', 42),
|
|
141
|
+
'Execute': ('Execute', 43),
|
|
142
|
+
'PrintScreen': ('PrintScreen', 44),
|
|
143
|
+
'Help': ('Help', 47),
|
|
144
|
+
'ContextMenu': ('ContextMenu', 93),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if key in key_map:
|
|
148
|
+
return key_map[key]
|
|
149
|
+
|
|
150
|
+
# Handle alphanumeric keys dynamically
|
|
151
|
+
if len(key) == 1:
|
|
152
|
+
if key.isalpha():
|
|
153
|
+
# Letter keys: A-Z have VK codes 65-90
|
|
154
|
+
return (f'Key{key.upper()}', ord(key.upper()))
|
|
155
|
+
elif key.isdigit():
|
|
156
|
+
# Digit keys: 0-9 have VK codes 48-57 (same as ASCII)
|
|
157
|
+
return (f'Digit{key}', ord(key))
|
|
158
|
+
|
|
159
|
+
# Fallback: use the key name as code, no virtual key code
|
|
160
|
+
return (key, None)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Backward compatibility: provide standalone function
|
|
164
|
+
def get_key_info(key: str) -> tuple[str, int | None]:
|
|
165
|
+
"""Get the code and windowsVirtualKeyCode for a key.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
key: Key name (e.g., 'Enter', 'ArrowUp', 'a', 'A')
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Tuple of (code, windowsVirtualKeyCode)
|
|
172
|
+
|
|
173
|
+
Reference: Windows Virtual Key Codes
|
|
174
|
+
https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
|
175
|
+
"""
|
|
176
|
+
return Utils.get_key_info(key)
|