4cd2d4af2b
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Waiting to run
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Waiting to run
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
276 lines
10 KiB
Python
276 lines
10 KiB
Python
import pytest
|
|
from pytest_httpserver import HTTPServer
|
|
|
|
from browser_use.agent.views import ActionResult
|
|
from browser_use.browser import BrowserSession
|
|
from browser_use.browser.profile import BrowserProfile
|
|
from browser_use.tools.service import Tools
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def http_server():
|
|
"""Create and provide a test HTTP server that serves static content."""
|
|
server = HTTPServer()
|
|
server.start()
|
|
|
|
# Add route for ARIA menu test page
|
|
server.expect_request('/aria-menu').respond_with_data(
|
|
"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>ARIA Menu Test</title>
|
|
<style>
|
|
.menu {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
border: 1px solid #ccc;
|
|
background: white;
|
|
width: 200px;
|
|
}
|
|
.menu-item {
|
|
padding: 10px 20px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
.menu-item:hover {
|
|
background: #f0f0f0;
|
|
}
|
|
.menu-item-anchor {
|
|
text-decoration: none;
|
|
color: #333;
|
|
display: block;
|
|
}
|
|
#result {
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
min-height: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>ARIA Menu Test</h1>
|
|
<p>This menu uses ARIA roles instead of native select elements</p>
|
|
|
|
<!-- Exactly like the HTML provided in the issue -->
|
|
<ul class="menu menu-format-standard menu-regular" role="menu" id="pyNavigation1752753375773" style="display: block;">
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Filter</span></span>
|
|
</a>
|
|
</li>
|
|
<li class="menu-item menu-item-enabled" role="presentation" id="menu-item-$PpyNavigation1752753375773$ppyElements$l2">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor menu-item-expand" tabindex="0" role="menuitem" aria-haspopup="true">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Sort</span></span>
|
|
</a>
|
|
<div class="menu-panel-wrapper">
|
|
<ul class="menu menu-format-standard menu-regular" role="menu" id="$PpyNavigation1752753375773$ppyElements$l2">
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Lowest to highest</span></span>
|
|
</a>
|
|
</li>
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Highest to lowest</span></span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</li>
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Appearance</span></span>
|
|
</a>
|
|
</li>
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Summarize</span></span>
|
|
</a>
|
|
</li>
|
|
<li class="menu-item menu-item-enabled" role="presentation">
|
|
<a href="#" onclick="pd(event);" class="menu-item-anchor" tabindex="0" role="menuitem">
|
|
<span class="menu-item-title-wrap"><span class="menu-item-title">Delete</span></span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div id="result">Click an option to see the result</div>
|
|
|
|
<script>
|
|
// Mock the pd function that prevents default
|
|
function pd(event) {
|
|
event.preventDefault();
|
|
const text = event.target.closest('[role="menuitem"]').textContent.trim();
|
|
document.getElementById('result').textContent = 'Clicked: ' + text;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
""",
|
|
content_type='text/html',
|
|
)
|
|
|
|
yield server
|
|
server.stop()
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def base_url(http_server):
|
|
"""Return the base URL for the test HTTP server."""
|
|
return f'http://{http_server.host}:{http_server.port}'
|
|
|
|
|
|
@pytest.fixture(scope='module')
|
|
async def browser_session():
|
|
"""Create and provide a Browser instance with security disabled."""
|
|
browser_session = BrowserSession(
|
|
browser_profile=BrowserProfile(
|
|
headless=True,
|
|
user_data_dir=None,
|
|
keep_alive=True,
|
|
chromium_sandbox=False, # Disable sandbox for CI environment
|
|
)
|
|
)
|
|
await browser_session.start()
|
|
yield browser_session
|
|
await browser_session.kill()
|
|
|
|
|
|
@pytest.fixture(scope='function')
|
|
def tools():
|
|
"""Create and provide a Tools instance."""
|
|
return Tools()
|
|
|
|
|
|
class TestARIAMenuDropdown:
|
|
"""Test ARIA menu support for get_dropdown_options and select_dropdown_option."""
|
|
|
|
@pytest.mark.skip(reason='TODO: fix')
|
|
async def test_get_dropdown_options_with_aria_menu(self, tools, browser_session: BrowserSession, base_url):
|
|
"""Test that get_dropdown_options can retrieve options from ARIA menus."""
|
|
# Navigate to the ARIA menu test page
|
|
await tools.navigate(url=f'{base_url}/aria-menu', new_tab=False, browser_session=browser_session)
|
|
|
|
# Wait for the page to load
|
|
from browser_use.browser.events import NavigationCompleteEvent
|
|
|
|
await browser_session.event_bus.expect(NavigationCompleteEvent, timeout=10.0)
|
|
|
|
# Initialize the DOM state to populate the selector map
|
|
await browser_session.get_browser_state_summary()
|
|
|
|
# Find the ARIA menu element by ID
|
|
menu_index = await browser_session.get_index_by_id('pyNavigation1752753375773')
|
|
|
|
assert menu_index is not None, 'Could not find ARIA menu element'
|
|
|
|
# Execute the action with the menu index
|
|
result = await tools.dropdown_options(index=menu_index, browser_session=browser_session)
|
|
|
|
# Verify the result structure
|
|
assert isinstance(result, ActionResult)
|
|
assert result.extracted_content is not None
|
|
|
|
# Expected ARIA menu options
|
|
expected_options = ['Filter', 'Sort', 'Appearance', 'Summarize', 'Delete']
|
|
|
|
# Verify all options are returned
|
|
for option in expected_options:
|
|
assert option in result.extracted_content, f"Option '{option}' not found in result content"
|
|
|
|
# Verify the instruction for using the text in select_dropdown is included
|
|
assert 'Use the exact text string in select_dropdown' in result.extracted_content
|
|
|
|
@pytest.mark.skip(reason='TODO: fix')
|
|
async def test_select_dropdown_option_with_aria_menu(self, tools, browser_session: BrowserSession, base_url):
|
|
"""Test that select_dropdown_option can select an option from ARIA menus."""
|
|
# Navigate to the ARIA menu test page
|
|
await tools.navigate(url=f'{base_url}/aria-menu', new_tab=False, browser_session=browser_session)
|
|
|
|
# Wait for the page to load
|
|
from browser_use.browser.events import NavigationCompleteEvent
|
|
|
|
await browser_session.event_bus.expect(NavigationCompleteEvent, timeout=10.0)
|
|
|
|
# Initialize the DOM state to populate the selector map
|
|
await browser_session.get_browser_state_summary()
|
|
|
|
# Find the ARIA menu element by ID
|
|
menu_index = await browser_session.get_index_by_id('pyNavigation1752753375773')
|
|
|
|
assert menu_index is not None, 'Could not find ARIA menu element'
|
|
|
|
# Execute the action with the menu index to select "Filter"
|
|
result = await tools.select_dropdown(index=menu_index, text='Filter', browser_session=browser_session)
|
|
|
|
# Verify the result structure
|
|
assert isinstance(result, ActionResult)
|
|
|
|
# Core logic validation: Verify selection was successful
|
|
assert result.extracted_content is not None
|
|
assert 'selected option' in result.extracted_content.lower() or 'clicked' in result.extracted_content.lower()
|
|
assert 'Filter' in result.extracted_content
|
|
|
|
# Verify the click actually had an effect on the page using CDP
|
|
cdp_session = await browser_session.get_or_create_cdp_session()
|
|
result = await cdp_session.cdp_client.send.Runtime.evaluate(
|
|
params={'expression': "document.getElementById('result').textContent", 'returnByValue': True},
|
|
session_id=cdp_session.session_id,
|
|
)
|
|
result_text = result.get('result', {}).get('value', '')
|
|
assert 'Filter' in result_text, f"Expected 'Filter' in result text, got '{result_text}'"
|
|
|
|
@pytest.mark.skip(reason='TODO: fix')
|
|
async def test_get_dropdown_options_with_nested_aria_menu(self, tools, browser_session: BrowserSession, base_url):
|
|
"""Test that get_dropdown_options can handle nested ARIA menus (like Sort submenu)."""
|
|
# Navigate to the ARIA menu test page
|
|
await tools.navigate(url=f'{base_url}/aria-menu', new_tab=False, browser_session=browser_session)
|
|
|
|
# Wait for the page to load
|
|
from browser_use.browser.events import NavigationCompleteEvent
|
|
|
|
await browser_session.event_bus.expect(NavigationCompleteEvent, timeout=10.0)
|
|
|
|
# Initialize the DOM state to populate the selector map
|
|
await browser_session.get_browser_state_summary()
|
|
|
|
# Get the selector map
|
|
selector_map = await browser_session.get_selector_map()
|
|
|
|
# Find the nested ARIA menu element in the selector map
|
|
nested_menu_index = None
|
|
for idx, element in selector_map.items():
|
|
# Look for the nested UL with id containing "$PpyNavigation"
|
|
if (
|
|
element.tag_name.lower() == 'ul'
|
|
and '$PpyNavigation' in str(element.attributes.get('id', ''))
|
|
and element.attributes.get('role') == 'menu'
|
|
):
|
|
nested_menu_index = idx
|
|
break
|
|
|
|
# The nested menu might not be in the selector map initially if it's hidden
|
|
# In that case, we should test the main menu
|
|
if nested_menu_index is None:
|
|
# Find the main menu instead
|
|
for idx, element in selector_map.items():
|
|
if element.tag_name.lower() == 'ul' and element.attributes.get('id') == 'pyNavigation1752753375773':
|
|
nested_menu_index = idx
|
|
break
|
|
|
|
assert nested_menu_index is not None, (
|
|
f'Could not find any ARIA menu element in selector map. Available elements: {[f"{idx}: {element.tag_name}" for idx, element in selector_map.items()]}'
|
|
)
|
|
|
|
# Execute the action with the menu index
|
|
result = await tools.dropdown_options(index=nested_menu_index, browser_session=browser_session)
|
|
|
|
# Verify the result structure
|
|
assert isinstance(result, ActionResult)
|
|
assert result.extracted_content is not None
|
|
|
|
# The action should return some menu options
|
|
assert 'Use the exact text string in select_dropdown' in result.extracted_content
|