重开仓库内容:改推五个技能(browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)
按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。 browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
This commit is contained in:
1 parent
9a152a1952
commit
777f7fe5d0
240 files changed
+52518
-4409
No files matched your search
@@ -0,0 +1,193 @@
|
||||
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")
|
||||
Reference in new issue
Block a user