Introduction to Python
Python is a versatile programming language used in web development, data science, and automation.
Read more"""Tests for search_page and find_elements actions.""" import asyncio import pytest from pytest_httpserver import HTTPServer from browser_use.agent.views import ActionResult from browser_use.browser import BrowserProfile, BrowserSession from browser_use.tools.service import Tools # --- Fixtures --- @pytest.fixture(scope='session') def http_server(): """Test HTTP server serving pages for search/find tests.""" server = HTTPServer() server.start() server.expect_request('/products').respond_with_data( """
| Name | Price | Rating |
|---|---|---|
| Widget A | $29.99 | 4.5 stars |
| Widget B | $49.99 | 4.2 stars |
| Gadget C | $19.50 | 3.8 stars |
| Gadget D | $99.00 | 4.9 stars |
Python is a versatile programming language used in web development, data science, and automation.
Read moreJavaScript powers the interactive web. Learn about DOM manipulation and event handling.
Read moreMaster CSS Grid, Flexbox, and custom properties for modern web layouts.
Read moreThe Quick Brown Fox jumps over the lazy dog.
QUICK BROWN FOX is an uppercase variant.
quick brown fox is a lowercase variant.
""", content_type='text/html', ) # /images-page route is registered dynamically in base_url fixture once port is known yield server server.stop() @pytest.fixture(scope='session') def base_url(http_server): url = f'http://{http_server.host}:{http_server.port}' # Register images page here so we can embed the absolute img src URL http_server.expect_request('/images-page').respond_with_data( f"""
""",
content_type='text/html',
)
return url
@pytest.fixture(scope='module')
async def browser_session():
session = BrowserSession(
browser_profile=BrowserProfile(
headless=True,
user_data_dir=None,
keep_alive=True,
)
)
await session.start()
yield session
await session.kill()
@pytest.fixture(scope='function')
def tools():
return Tools()
# --- Helper ---
async def _navigate_and_wait(tools, browser_session, url):
"""Navigate to URL and wait for page load."""
await tools.navigate(url=url, new_tab=False, browser_session=browser_session)
await asyncio.sleep(0.5)
# --- search_page tests ---
class TestSearchPage:
"""Tests for the search_page action."""
async def test_literal_text_search(self, tools, browser_session, base_url):
"""Literal text search finds matches with context."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern='Widget A', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert 'Widget A' in result.extracted_content
assert '1 match' in result.extracted_content
async def test_regex_search_prices(self, tools, browser_session, base_url):
"""Regex search finds all price patterns on the page."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern=r'\$\d+\.\d{2}', regex=True, browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
# Should find $29.99, $49.99, $19.50, $99.00
assert '4 matches' in result.extracted_content
assert '$29.99' in result.extracted_content
assert '$49.99' in result.extracted_content
async def test_css_scope_limits_search(self, tools, browser_session, base_url):
"""css_scope limits search to elements within the selector."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
# Search only in footer
result = await tools.search_page(pattern='price', css_scope='#footer', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
# "Best price guarantee" is in footer
assert '1 match' in result.extracted_content
assert 'guarantee' in result.extracted_content
async def test_case_insensitive_default(self, tools, browser_session, base_url):
"""Search is case-insensitive by default."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/case-test')
result = await tools.search_page(pattern='quick brown fox', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
# Should match all three variants (Quick, QUICK, quick)
assert '3 matches' in result.extracted_content
async def test_case_sensitive(self, tools, browser_session, base_url):
"""case_sensitive=True restricts to exact case."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/case-test')
result = await tools.search_page(pattern='QUICK BROWN FOX', case_sensitive=True, browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert '1 match' in result.extracted_content
async def test_max_results(self, tools, browser_session, base_url):
"""max_results limits the number of returned matches."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern=r'\$\d+\.\d{2}', regex=True, max_results=2, browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
# Total should still show 4, but only 2 are displayed
assert '4 matches' in result.extracted_content
assert 'Increase max_results' in result.extracted_content
async def test_no_matches(self, tools, browser_session, base_url):
"""No matches returns a clean message, not an error."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern='xyznonexistent', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert 'No matches found' in result.extracted_content
async def test_element_path_in_results(self, tools, browser_session, base_url):
"""Matches include the element path for context."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern='guarantee', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
# Should show element path containing footer
assert '(in' in result.extracted_content
async def test_invalid_css_scope(self, tools, browser_session, base_url):
"""Invalid css_scope returns a clear error."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern='test', css_scope='#nonexistent-scope', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is not None
assert 'scope' in result.error.lower() or 'not found' in result.error.lower()
async def test_memory_set(self, tools, browser_session, base_url):
"""long_term_memory is set with match count summary."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.search_page(pattern='Widget', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.long_term_memory is not None
assert 'Widget' in result.long_term_memory
assert 'match' in result.long_term_memory
# --- find_elements tests ---
class TestFindElements:
"""Tests for the find_elements action."""
async def test_basic_selector(self, tools, browser_session, base_url):
"""Basic CSS selector returns correct elements."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.find_elements(selector='tr.product-row', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert '4 elements' in result.extracted_content
assert 'Widget A' in result.extracted_content
assert 'Gadget D' in result.extracted_content
async def test_attribute_extraction(self, tools, browser_session, base_url):
"""attributes parameter extracts specific attributes from elements."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.find_elements(
selector='a.page-link',
attributes=['href', 'class'],
browser_session=browser_session,
)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert '3 elements' in result.extracted_content
assert 'href=' in result.extracted_content
assert '/products?page=' in result.extracted_content
async def test_max_results_limiting(self, tools, browser_session, base_url):
"""max_results limits displayed elements while showing total count."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.find_elements(selector='tr.product-row', max_results=2, browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert '4 elements' in result.extracted_content
assert 'Showing 2 of 4' in result.extracted_content
async def test_no_matching_elements(self, tools, browser_session, base_url):
"""No matches returns a clean message, not an error."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.find_elements(selector='div.nonexistent', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert 'No elements found' in result.extracted_content
async def test_invalid_selector(self, tools, browser_session, base_url):
"""Invalid CSS selector returns a clear error, not a crash."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/products')
result = await tools.find_elements(selector='[[[invalid', browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is not None
assert 'selector' in result.error.lower() or 'invalid' in result.error.lower()
async def test_include_text_false(self, tools, browser_session, base_url):
"""include_text=False omits text content from results."""
await _navigate_and_wait(tools, browser_session, f'{base_url}/articles')
result = await tools.find_elements(selector='article', include_text=False, browser_session=browser_session)
assert isinstance(result, ActionResult)
assert result.error is None
assert result.extracted_content is not None
assert '3 elements' in result.extracted_content
# Text content should not appear (no article body text)
# But the tag and children count should still be present
assert '