Files
browser-use--browser-harness/tests/integration/test_js.py
T
2026-07-13 12:45:10 +08:00

194 lines
6.3 KiB
Python

from unittest.mock import patch
import math
import pytest
from browser_harness import helpers
def _capture_cdp():
captured = []
def fake_cdp(method, **kwargs):
captured.append((method, kwargs))
return {"result": {"value": None}}
return fake_cdp, captured
def _evaluated_expression(captured):
return next(kw["expression"] for m, kw in captured if m == "Runtime.evaluate")
def _evaluated_expressions(captured):
return [kw["expression"] for m, kw in captured if m == "Runtime.evaluate"]
def _illegal_return_then_success():
captured = []
def fake_cdp(method, **kwargs):
captured.append((method, kwargs))
if method == "Runtime.evaluate" and len(_evaluated_expressions(captured)) == 1:
return {
"result": {"type": "object", "subtype": "error", "description": "SyntaxError: Illegal return statement"},
"exceptionDetails": {"text": "Uncaught SyntaxError: Illegal return statement"},
}
return {"result": {"value": None}}
return fake_cdp, captured
def test_simple_expression_passes_through():
fake_cdp, captured = _capture_cdp()
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js("document.title")
assert _evaluated_expression(captured) == "document.title"
def test_return_statement_gets_wrapped():
fake_cdp, captured = _illegal_return_then_success()
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js("const x = 1; return x")
assert _evaluated_expressions(captured) == [
"const x = 1; return x",
"(function(){const x = 1; return x})()",
]
def test_iife_with_internal_return_is_not_double_wrapped():
fake_cdp, captured = _capture_cdp()
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js("(function(){ return document.title; })()")
assert _evaluated_expression(captured) == "(function(){ return document.title; })()"
def test_js_raises_on_syntax_error_exception_details():
def fake_cdp(method, **kwargs):
return {
"result": {
"type": "object",
"subtype": "error",
"description": "SyntaxError: Invalid or unexpected token",
},
"exceptionDetails": {
"text": "Uncaught",
"lineNumber": 1,
"columnNumber": 12,
},
}
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
with pytest.raises(RuntimeError, match="SyntaxError"):
helpers.js('return "a\n\nb";')
def test_js_raises_on_runtime_error_exception_details():
def fake_cdp(method, **kwargs):
return {
"result": {
"type": "object",
"subtype": "error",
"description": "ReferenceError: missing is not defined",
},
"exceptionDetails": {
"text": "Uncaught",
"lineNumber": 0,
"columnNumber": 17,
},
}
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
with pytest.raises(RuntimeError, match="ReferenceError"):
helpers.js("return missing.value")
def test_js_raises_on_error_result_without_exception_details():
def fake_cdp(method, **kwargs):
return {
"result": {
"type": "object",
"subtype": "error",
"description": "Error: evaluation failed",
}
}
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
with pytest.raises(RuntimeError, match="evaluation failed"):
helpers.js("throw new Error('evaluation failed')")
def test_return_word_inside_string_does_not_trigger_wrapping():
fake_cdp, captured = _capture_cdp()
expr = 'document.body.innerText.includes("return ")'
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js(expr)
assert _evaluated_expression(captured) == expr
def test_return_word_inside_comment_does_not_trigger_wrapping():
fake_cdp, captured = _capture_cdp()
expr = "// return comment\n1 + 1"
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js(expr)
assert _evaluated_expression(captured) == expr
@pytest.mark.parametrize("expr", ["return\t1", "return\n1"])
def test_top_level_return_with_whitespace_gets_wrapped(expr):
fake_cdp, captured = _illegal_return_then_success()
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
helpers.js(expr)
assert _evaluated_expressions(captured) == [expr, f"(function(){{{expr}}})()"]
@pytest.mark.parametrize(
("unserializable", "expected"),
[
("NaN", math.nan),
("Infinity", math.inf),
("-Infinity", -math.inf),
("-0", -0.0),
("1n", 1),
],
)
def test_js_returns_unserializable_values(unserializable, expected):
def fake_cdp(method, **kwargs):
return {"result": {"type": "number", "unserializableValue": unserializable, "description": unserializable}}
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
value = helpers.js(unserializable)
if isinstance(expected, float) and math.isnan(expected):
assert math.isnan(value)
elif expected == 0:
assert value == 0
assert math.copysign(1, value) == math.copysign(1, expected)
else:
assert value == expected
def test_js_primitive_exception_message_uses_exception_value():
def fake_cdp(method, **kwargs):
return {
"result": {"type": "string", "value": "boom"},
"exceptionDetails": {
"text": "Uncaught",
"lineNumber": 0,
"columnNumber": 9,
"exception": {"type": "string", "value": "boom"},
},
}
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
with pytest.raises(RuntimeError, match="boom"):
helpers.js("throw value")
def test_js_timeout_error_includes_expression_context():
def fake_cdp(method, **kwargs):
raise TimeoutError("timed out")
with patch("browser_harness.helpers.cdp", side_effect=fake_cdp):
with pytest.raises(RuntimeError, match="Runtime.evaluate.*document.title"):
helpers.js("document.title")