Skip to content

Commit d25fa47

Browse files
authored
In evaluate, only set serialization_options if return_by_value=False (#196)
* In evaluate, only set serialization_options if return_by_value=False Fixes #195: now calling evaluate with return_by_value=True and an expression that evaluates to a JSON object will return the corresponding Python object. A consequence of this change is that calling evaluate with return_by_value=True and an expression that evalutates to a non-JSON result now result in a ProtocolException. Callers may need to update their code to pass return_by_value=False. * Update CHANGELOG.md for fix for #195.
1 parent ec5f257 commit d25fa47

File tree

4 files changed

+60
-24
lines changed

4 files changed

+60
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
- Fix for calls to `evaluate` that return JSON: only set `serialization_options` when `return_by_value=False` @thromer
13+
1214
### Added
1315

1416
### Changed

tests/core/test_tab.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from tests.sample_data import sample_file
88
from zendriver.cdp.fetch import RequestStage
99
from zendriver.cdp.network import ResourceType
10+
from zendriver.core.connection import ProtocolException
1011

1112

1213
async def test_set_user_agent_sets_navigator_values(browser: zd.Browser) -> None:
@@ -295,53 +296,74 @@ async def test_evaluate_complex_object_no_error(browser: zd.Browser) -> None:
295296
tab = await browser.get(sample_file("complex_object.html"))
296297
await tab.wait_for_ready_state("complete")
297298

298-
result = await tab.evaluate("document.querySelector('body:not(.no-js)')")
299+
result = await tab.evaluate(
300+
"document.querySelector('body:not(.no-js)')", return_by_value=False
301+
)
299302
assert result is not None
300303

301304
# This is similar to the original failing case but more likely to trigger the error
302-
body_with_complex_refs = await tab.evaluate("document.body")
305+
body_with_complex_refs = await tab.evaluate("document.body", return_by_value=False)
303306
assert body_with_complex_refs is not None
304307

305308

306-
async def test_evaluate_return_by_value_modes(browser: zd.Browser) -> None:
309+
async def test_evaluate_return_by_value_complex_object(browser: zd.Browser) -> None:
307310
tab = await browser.get(sample_file("complex_object.html"))
308311
await tab.wait_for_ready_state("complete")
309312

310313
expression = "document.querySelector('body:not(.no-js)')"
311314

312-
result_by_value = await tab.evaluate(expression, return_by_value=True)
313-
assert result_by_value is not None
315+
# Fetching a complex object with return_by_value=True is unsupported because there is no
316+
# way to represent the object as a simple data structure.
317+
with pytest.raises(ProtocolException):
318+
_ = await tab.evaluate(expression, return_by_value=True)
314319

315-
result_false = await tab.evaluate(expression, return_by_value=False)
320+
result_by_value_false = await tab.evaluate(expression, return_by_value=False)
316321
assert (
317-
result_false is not None
322+
result_by_value_false is not None
318323
) # Should return the deep serialized value, not a tuple
319324

320325

326+
async def test_evaluate_return_by_value_simple_json(browser: zd.Browser) -> None:
327+
tab = await browser.get(sample_file("simple_json.html"))
328+
await tab.wait_for_ready_state("complete")
329+
330+
expression = "JSON.parse(document.querySelector('#obj').textContent)"
331+
332+
result_by_value_true = await tab.evaluate(expression, return_by_value=True)
333+
assert result_by_value_true == {"a": "x", "b": 3.14159}
334+
335+
result_by_value_false = await tab.evaluate(expression, return_by_value=False)
336+
assert result_by_value_false == [
337+
["a", {"type": "string", "value": "x"}],
338+
["b", {"type": "number", "value": 3.14159}],
339+
]
340+
341+
321342
async def test_evaluate_stress_test_complex_objects(browser: zd.Browser) -> None:
322343
tab = await browser.get(sample_file("complex_object.html"))
323344
await tab.wait_for_ready_state("complete")
324345

325346
# Test various DOM queries that could trigger reference chain issues
326-
# Each test case is a tuple of (expression, expected_type_or_validator)
347+
# Each test case is a tuple of (expression, return_by_value, expected_type_or_validator)
327348
test_cases = [
328-
("document.querySelector('body:not(.no-js)')", lambda x: x is not None),
329-
("document.documentElement", lambda x: x is not None),
330-
("document.querySelector('*')", lambda x: x is not None),
331-
("document.body.parentElement", lambda x: x is not None),
332-
("document.getElementById('content')", lambda x: x is not None),
349+
("document.querySelector('body:not(.no-js)')", False, lambda x: x is not None),
350+
("document.documentElement", False, lambda x: x is not None),
351+
("document.querySelector('*')", False, lambda x: x is not None),
352+
("document.body.parentElement", False, lambda x: x is not None),
353+
("document.getElementById('content')", False, lambda x: x is not None),
333354
(
334355
"document.body.complexStructure ? 'has complex structure' : 'no structure'",
356+
True,
335357
str,
336358
),
337-
("document.readyState", str),
338-
("navigator.userAgent", str),
339-
("window.location.href", str),
340-
("document.title", str),
359+
("document.readyState", True, str),
360+
("navigator.userAgent", True, str),
361+
("window.location.href", True, str),
362+
("document.title", True, str),
341363
]
342364

343-
for expression, validator in test_cases:
344-
result = await tab.evaluate(expression)
365+
for expression, return_by_value, validator in test_cases:
366+
result = await tab.evaluate(expression, return_by_value=return_by_value)
345367
# Verify the result is usable and matches expected type/validation
346368
if callable(validator):
347369
assert validator(

tests/sample_data/simple_json.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Simple JSON object</title>
6+
</head>
7+
<body>
8+
<pre id="obj">{"a": "x", "b": 3.14159}</pre>
9+
</body>
10+
</html>

zendriver/core/tab.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -718,11 +718,13 @@ async def evaluate(
718718
| None
719719
| typing.Tuple[cdp.runtime.RemoteObject, cdp.runtime.ExceptionDetails | None]
720720
):
721-
ser = cdp.runtime.SerializationOptions(
722-
serialization="deep",
723-
max_depth=10,
724-
additional_parameters={"maxNodeDepth": 10, "includeShadowTree": "all"},
725-
)
721+
ser: cdp.runtime.SerializationOptions | None = None
722+
if not return_by_value:
723+
ser = cdp.runtime.SerializationOptions(
724+
serialization="deep",
725+
max_depth=10,
726+
additional_parameters={"maxNodeDepth": 10, "includeShadowTree": "all"},
727+
)
726728

727729
remote_object, errors = await self.send(
728730
cdp.runtime.evaluate(

0 commit comments

Comments
 (0)