[Add] browser-use and main.py
This commit is contained in:
parent
08e64bdf45
commit
96914d44ac
221 changed files with 30952 additions and 1 deletions
123
browser-use/browser_use/dom/tests/debug_page_structure.py
Normal file
123
browser-use/browser_use/dom/tests/debug_page_structure.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from browser_use.browser.browser import Browser, BrowserConfig
|
||||
from browser_use.browser.context import BrowserContext
|
||||
|
||||
|
||||
async def analyze_page_structure(url: str):
|
||||
"""Analyze and print the structure of a webpage with enhanced debugging"""
|
||||
browser = Browser(
|
||||
config=BrowserConfig(
|
||||
headless=False, # Set to True if you don't need to see the browser
|
||||
)
|
||||
)
|
||||
|
||||
context = BrowserContext(browser=browser)
|
||||
|
||||
try:
|
||||
async with context as ctx:
|
||||
# Navigate to the URL
|
||||
page = await ctx.get_current_page()
|
||||
await page.goto(url)
|
||||
await page.wait_for_load_state('networkidle')
|
||||
|
||||
# Get viewport dimensions
|
||||
viewport_info = await page.evaluate("""() => {
|
||||
return {
|
||||
viewport: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
scrollX: window.scrollX,
|
||||
scrollY: window.scrollY
|
||||
}
|
||||
}
|
||||
}""")
|
||||
|
||||
print('\nViewport Information:')
|
||||
print(f'Width: {viewport_info["viewport"]["width"]}')
|
||||
print(f'Height: {viewport_info["viewport"]["height"]}')
|
||||
print(f'ScrollX: {viewport_info["viewport"]["scrollX"]}')
|
||||
print(f'ScrollY: {viewport_info["viewport"]["scrollY"]}')
|
||||
|
||||
# Enhanced debug information for cookie consent and fixed position elements
|
||||
debug_info = await page.evaluate("""() => {
|
||||
function getElementInfo(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(element);
|
||||
return {
|
||||
tag: element.tagName.toLowerCase(),
|
||||
id: element.id,
|
||||
className: element.className,
|
||||
position: style.position,
|
||||
rect: {
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
},
|
||||
isFixed: style.position === 'fixed',
|
||||
isSticky: style.position === 'sticky',
|
||||
zIndex: style.zIndex,
|
||||
visibility: style.visibility,
|
||||
display: style.display,
|
||||
opacity: style.opacity
|
||||
};
|
||||
}
|
||||
|
||||
// Find cookie-related elements
|
||||
const cookieElements = Array.from(document.querySelectorAll('[id*="cookie"], [id*="consent"], [class*="cookie"], [class*="consent"]'));
|
||||
const fixedElements = Array.from(document.querySelectorAll('*')).filter(el => {
|
||||
const style = window.getComputedStyle(el);
|
||||
return style.position === 'fixed' || style.position === 'sticky';
|
||||
});
|
||||
|
||||
return {
|
||||
cookieElements: cookieElements.map(getElementInfo),
|
||||
fixedElements: fixedElements.map(getElementInfo)
|
||||
};
|
||||
}""")
|
||||
|
||||
print('\nCookie-related Elements:')
|
||||
for elem in debug_info['cookieElements']:
|
||||
print(f'\nElement: {elem["tag"]}#{elem["id"]} .{elem["className"]}')
|
||||
print(f'Position: {elem["position"]}')
|
||||
print(f'Rect: {elem["rect"]}')
|
||||
print(f'Z-Index: {elem["zIndex"]}')
|
||||
print(f'Visibility: {elem["visibility"]}')
|
||||
print(f'Display: {elem["display"]}')
|
||||
print(f'Opacity: {elem["opacity"]}')
|
||||
|
||||
print('\nFixed/Sticky Position Elements:')
|
||||
for elem in debug_info['fixedElements']:
|
||||
print(f'\nElement: {elem["tag"]}#{elem["id"]} .{elem["className"]}')
|
||||
print(f'Position: {elem["position"]}')
|
||||
print(f'Rect: {elem["rect"]}')
|
||||
print(f'Z-Index: {elem["zIndex"]}')
|
||||
|
||||
print(f'\nPage Structure for {url}:\n')
|
||||
structure = await ctx.get_page_structure()
|
||||
print(structure)
|
||||
|
||||
input('Press Enter to close the browser...')
|
||||
finally:
|
||||
await browser.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# You can modify this URL to analyze different pages
|
||||
|
||||
urls = [
|
||||
'https://www.mlb.com/yankees/stats/',
|
||||
'https://immobilienscout24.de',
|
||||
'https://www.zeiss.com/career/en/job-search.html?page=1',
|
||||
'https://www.zeiss.com/career/en/job-search.html?page=1',
|
||||
'https://reddit.com',
|
||||
]
|
||||
for url in urls:
|
||||
asyncio.run(analyze_page_structure(url))
|
||||
182
browser-use/browser_use/dom/tests/extraction_test.py
Normal file
182
browser-use/browser_use/dom/tests/extraction_test.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import asyncio
|
||||
import os
|
||||
|
||||
import anyio
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from browser_use.agent.prompts import AgentMessagePrompt
|
||||
from browser_use.browser.browser import Browser, BrowserConfig
|
||||
from browser_use.browser.context import BrowserContext, BrowserContextConfig
|
||||
from browser_use.dom.service import DomService
|
||||
|
||||
|
||||
def count_string_tokens(string: str, model: str) -> tuple[int, float]:
|
||||
"""Count the number of tokens in a string using a specified model."""
|
||||
|
||||
def get_price_per_token(model: str) -> float:
|
||||
"""Get the price per token for a specified model.
|
||||
|
||||
@todo: move to utils, use a package or sth
|
||||
"""
|
||||
prices = {
|
||||
'gpt-4o': 2.5 / 1e6,
|
||||
'gpt-4o-mini': 0.15 / 1e6,
|
||||
}
|
||||
return prices[model]
|
||||
|
||||
llm = ChatOpenAI(model=model)
|
||||
token_count = llm.get_num_tokens(string)
|
||||
price = token_count * get_price_per_token(model)
|
||||
return token_count, price
|
||||
|
||||
|
||||
TIMEOUT = 60
|
||||
|
||||
DEFAULT_INCLUDE_ATTRIBUTES = [
|
||||
'id',
|
||||
'title',
|
||||
'type',
|
||||
'name',
|
||||
'role',
|
||||
'aria-label',
|
||||
'placeholder',
|
||||
'value',
|
||||
'alt',
|
||||
'aria-expanded',
|
||||
'data-date-format',
|
||||
]
|
||||
|
||||
|
||||
async def test_focus_vs_all_elements():
|
||||
config = BrowserContextConfig(
|
||||
# cookies_file='cookies3.json',
|
||||
disable_security=True,
|
||||
wait_for_network_idle_page_load_time=1,
|
||||
)
|
||||
|
||||
browser = Browser(
|
||||
config=BrowserConfig(
|
||||
# browser_binary_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
)
|
||||
)
|
||||
context = BrowserContext(browser=browser, config=config)
|
||||
|
||||
websites = [
|
||||
'https://demos.telerik.com/kendo-react-ui/treeview/overview/basic/func?theme=default-ocean-blue-a11y',
|
||||
'https://www.ycombinator.com/companies',
|
||||
'https://kayak.com/flights',
|
||||
# 'https://en.wikipedia.org/wiki/Humanist_Party_of_Ontario',
|
||||
# 'https://www.google.com/travel/flights?tfs=CBwQARoJagcIARIDTEpVGglyBwgBEgNMSlVAAUgBcAGCAQsI____________AZgBAQ&tfu=KgIIAw&hl=en-US&gl=US',
|
||||
# # 'https://www.concur.com/?&cookie_preferences=cpra',
|
||||
# 'https://immobilienscout24.de',
|
||||
'https://docs.google.com/spreadsheets/d/1INaIcfpYXlMRWO__de61SHFCaqt1lfHlcvtXZPItlpI/edit',
|
||||
'https://www.zeiss.com/career/en/job-search.html?page=1',
|
||||
'https://www.mlb.com/yankees/stats/',
|
||||
'https://www.amazon.com/s?k=laptop&s=review-rank&crid=1RZCEJ289EUSI&qid=1740202453&sprefix=laptop%2Caps%2C166&ref=sr_st_review-rank&ds=v1%3A4EnYKXVQA7DIE41qCvRZoNB4qN92Jlztd3BPsTFXmxU',
|
||||
'https://reddit.com',
|
||||
'https://codepen.io/geheimschriftstift/pen/mPLvQz',
|
||||
'https://www.google.com/search?q=google+hi&oq=google+hi&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRhA0gEIMjI2NmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8',
|
||||
'https://google.com',
|
||||
'https://amazon.com',
|
||||
'https://github.com',
|
||||
]
|
||||
|
||||
async with context as context:
|
||||
page = await context.get_current_page()
|
||||
dom_service = DomService(page)
|
||||
|
||||
for website in websites:
|
||||
# sleep 2
|
||||
await page.goto(website)
|
||||
asyncio.sleep(1)
|
||||
|
||||
last_clicked_index = None # Track the index for text input
|
||||
while True:
|
||||
try:
|
||||
print(f'\n{"=" * 50}\nTesting {website}\n{"=" * 50}')
|
||||
|
||||
# Get/refresh the state (includes removing old highlights)
|
||||
print('\nGetting page state...')
|
||||
all_elements_state = await context.get_state(True)
|
||||
|
||||
selector_map = all_elements_state.selector_map
|
||||
total_elements = len(selector_map.keys())
|
||||
print(f'Total number of elements: {total_elements}')
|
||||
|
||||
# print(all_elements_state.element_tree.clickable_elements_to_string())
|
||||
prompt = AgentMessagePrompt(
|
||||
state=all_elements_state,
|
||||
result=None,
|
||||
include_attributes=DEFAULT_INCLUDE_ATTRIBUTES,
|
||||
step_info=None,
|
||||
)
|
||||
# print(prompt.get_user_message(use_vision=False).content)
|
||||
# Write the user message to a file for analysis
|
||||
user_message = prompt.get_user_message(use_vision=False).content
|
||||
os.makedirs('./tmp', exist_ok=True)
|
||||
async with await anyio.open_file('./tmp/user_message.txt', 'w', encoding='utf-8') as f:
|
||||
await f.write(user_message)
|
||||
|
||||
token_count, price = count_string_tokens(user_message, model='gpt-4o')
|
||||
print(f'Prompt token count: {token_count}, price: {round(price, 4)} USD')
|
||||
print('User message written to ./tmp/user_message.txt')
|
||||
|
||||
# also save all_elements_state.element_tree.clickable_elements_to_string() to a file
|
||||
# with open('./tmp/clickable_elements.json', 'w', encoding='utf-8') as f:
|
||||
# f.write(json.dumps(all_elements_state.element_tree.__json__(), indent=2))
|
||||
# print('Clickable elements written to ./tmp/clickable_elements.json')
|
||||
|
||||
answer = input("Enter element index to click, 'index,text' to input, or 'q' to quit: ")
|
||||
|
||||
if answer.lower() == 'q':
|
||||
break
|
||||
|
||||
try:
|
||||
if ',' in answer:
|
||||
# Input text format: index,text
|
||||
parts = answer.split(',', 1)
|
||||
if len(parts) == 2:
|
||||
try:
|
||||
target_index = int(parts[0].strip())
|
||||
text_to_input = parts[1]
|
||||
if target_index in selector_map:
|
||||
element_node = selector_map[target_index]
|
||||
print(
|
||||
f"Inputting text '{text_to_input}' into element {target_index}: {element_node.tag_name}"
|
||||
)
|
||||
await context._input_text_element_node(element_node, text_to_input)
|
||||
print('Input successful.')
|
||||
else:
|
||||
print(f'Invalid index: {target_index}')
|
||||
except ValueError:
|
||||
print(f'Invalid index format: {parts[0]}')
|
||||
else:
|
||||
print("Invalid input format. Use 'index,text'.")
|
||||
else:
|
||||
# Click element format: index
|
||||
try:
|
||||
clicked_index = int(answer)
|
||||
if clicked_index in selector_map:
|
||||
element_node = selector_map[clicked_index]
|
||||
print(f'Clicking element {clicked_index}: {element_node.tag_name}')
|
||||
await context._click_element_node(element_node)
|
||||
print('Click successful.')
|
||||
else:
|
||||
print(f'Invalid index: {clicked_index}')
|
||||
except ValueError:
|
||||
print(f"Invalid input: '{answer}'. Enter an index, 'index,text', or 'q'.")
|
||||
|
||||
except Exception as action_e:
|
||||
print(f'Action failed: {action_e}')
|
||||
|
||||
# No explicit highlight removal here, get_state handles it at the start of the loop
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error in loop: {e}')
|
||||
# Optionally add a small delay before retrying
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(test_focus_vs_all_elements())
|
||||
# asyncio.run(test_process_html_file()) # Commented out the other test
|
||||
43
browser-use/browser_use/dom/tests/process_dom_test.py
Normal file
43
browser-use/browser_use/dom/tests/process_dom_test.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import anyio
|
||||
|
||||
from browser_use.browser.browser import Browser, BrowserConfig
|
||||
|
||||
|
||||
async def test_process_dom():
|
||||
browser = Browser(config=BrowserConfig(headless=False))
|
||||
|
||||
async with await browser.new_context() as context:
|
||||
page = await context.get_current_page()
|
||||
await page.goto('https://kayak.com/flights')
|
||||
# await page.goto('https://google.com/flights')
|
||||
# await page.goto('https://immobilienscout24.de')
|
||||
# await page.goto('https://seleniumbase.io/w3schools/iframes')
|
||||
|
||||
await asyncio.sleep(3)
|
||||
|
||||
async with await anyio.open_file('browser_use/dom/buildDomTree.js', 'r') as f:
|
||||
js_code = await f.read()
|
||||
|
||||
start = time.time()
|
||||
dom_tree = await page.evaluate(js_code)
|
||||
end = time.time()
|
||||
|
||||
# print(dom_tree)
|
||||
print(f'Time: {end - start:.2f}s')
|
||||
|
||||
os.makedirs('./tmp', exist_ok=True)
|
||||
async with await anyio.open_file('./tmp/dom.json', 'w') as f:
|
||||
await f.write(json.dumps(dom_tree, indent=1))
|
||||
|
||||
# both of these work for immobilienscout24.de
|
||||
# await page.click('.sc-dcJsrY.ezjNCe')
|
||||
# await page.click(
|
||||
# 'div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div > div > button:nth-of-type(2)'
|
||||
# )
|
||||
|
||||
input('Press Enter to continue...')
|
||||
Loading…
Add table
Add a link
Reference in a new issue