Skip to content

Commit bb5a7f0

Browse files
thalissonvs3-Tokisaki-Kurumi
authored andcommitted
test: add tests for script execution with various element contexts
1 parent bc7e3a1 commit bb5a7f0

File tree

2 files changed

+296
-9
lines changed

2 files changed

+296
-9
lines changed

tests/test_browser/test_browser_tab.py

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
InvalidFileExtension,
1717
WaitElementTimeout,
1818
NetworkEventsNotEnabled,
19+
InvalidScriptWithElement,
1920
)
2021

2122
@pytest_asyncio.fixture
@@ -507,11 +508,11 @@ async def test_handle_dialog_no_dialog(self, tab):
507508

508509

509510
class TestTabScriptExecution:
510-
"""Test Tab JavaScript execution methods."""
511+
"""Test Tab script execution methods."""
511512

512513
@pytest.mark.asyncio
513514
async def test_execute_script_simple(self, tab):
514-
"""Test executing simple JavaScript."""
515+
"""Test execute_script with simple JavaScript."""
515516
tab._connection_handler.execute_command.return_value = {
516517
'result': {'result': {'value': 'Test Result'}}
517518
}
@@ -522,17 +523,130 @@ async def test_execute_script_simple(self, tab):
522523

523524
@pytest.mark.asyncio
524525
async def test_execute_script_with_element(self, tab):
525-
"""Test executing JavaScript with element context."""
526-
mock_element = WebElement(
527-
object_id='test-element-id',
528-
connection_handler=tab._connection_handler
529-
)
526+
"""Test execute_script with element context."""
527+
# Mock element
528+
element = MagicMock()
529+
element._object_id = 'test-object-id'
530530

531531
tab._connection_handler.execute_command.return_value = {
532532
'result': {'result': {'value': 'Element clicked'}}
533533
}
534534

535-
result = await tab.execute_script('this.click()', mock_element)
535+
result = await tab.execute_script('argument.click()', element)
536+
537+
tab._connection_handler.execute_command.assert_called_once()
538+
539+
@pytest.mark.asyncio
540+
async def test_execute_script_argument_without_element_raises_exception(self, tab):
541+
"""Test execute_script raises exception when script contains 'argument' but no element provided."""
542+
with pytest.raises(InvalidScriptWithElement) as exc_info:
543+
await tab.execute_script('argument.click()')
544+
545+
assert str(exc_info.value) == 'Script contains "argument" but no element was provided'
546+
tab._connection_handler.execute_command.assert_not_called()
547+
548+
@pytest.mark.asyncio
549+
async def test_execute_script_with_element_already_function(self, tab):
550+
"""Test execute_script with element when script is already a function."""
551+
element = MagicMock()
552+
element._object_id = 'test-object-id'
553+
554+
tab._connection_handler.execute_command.return_value = {
555+
'result': {'result': {'value': 'Function executed'}}
556+
}
557+
558+
# Script already wrapped in function
559+
script = 'function() { argument.click(); return "done"; }'
560+
result = await tab.execute_script(script, element)
561+
562+
tab._connection_handler.execute_command.assert_called_once()
563+
564+
@pytest.mark.asyncio
565+
async def test_execute_script_with_element_arrow_function(self, tab):
566+
"""Test execute_script with element when script is already an arrow function."""
567+
element = MagicMock()
568+
element._object_id = 'test-object-id'
569+
570+
tab._connection_handler.execute_command.return_value = {
571+
'result': {'result': {'value': 'Arrow function executed'}}
572+
}
573+
574+
# Script already wrapped in arrow function
575+
script = '() => { argument.click(); return "done"; }'
576+
result = await tab.execute_script(script, element)
577+
578+
tab._connection_handler.execute_command.assert_called_once()
579+
580+
@pytest.mark.asyncio
581+
async def test_execute_script_return_outside_function(self, tab):
582+
"""Test execute_script wraps return statement outside function."""
583+
tab._connection_handler.execute_command.return_value = {
584+
'result': {'result': {'value': 'Wrapped result'}}
585+
}
586+
587+
# Script with return outside function should be wrapped
588+
result = await tab.execute_script('return document.title')
589+
590+
tab._connection_handler.execute_command.assert_called_once()
591+
592+
@pytest.mark.asyncio
593+
async def test_execute_script_return_inside_function(self, tab):
594+
"""Test execute_script doesn't wrap when return is inside function."""
595+
tab._connection_handler.execute_command.return_value = {
596+
'result': {'result': {'value': 'Function result'}}
597+
}
598+
599+
# Script with return inside function should not be wrapped
600+
script = '''
601+
function getTitle() {
602+
return document.title;
603+
}
604+
getTitle();
605+
'''
606+
result = await tab.execute_script(script)
607+
608+
tab._connection_handler.execute_command.assert_called_once()
609+
610+
@pytest.mark.asyncio
611+
async def test_execute_script_no_return_statement(self, tab):
612+
"""Test execute_script without return statement."""
613+
tab._connection_handler.execute_command.return_value = {
614+
'result': {'result': {'value': None}}
615+
}
616+
617+
# Script without return should not be wrapped
618+
result = await tab.execute_script('console.log("Hello World")')
619+
620+
tab._connection_handler.execute_command.assert_called_once()
621+
622+
@pytest.mark.asyncio
623+
async def test_execute_script_with_comments_and_strings(self, tab):
624+
"""Test execute_script handles comments and strings correctly."""
625+
tab._connection_handler.execute_command.return_value = {
626+
'result': {'result': {'value': 'Test with comments'}}
627+
}
628+
629+
# Script with comments and strings containing 'return'
630+
script = '''
631+
// This comment has return in it
632+
var message = "This string has return in it";
633+
/* This block comment also has return */
634+
return "actual return";
635+
'''
636+
result = await tab.execute_script(script)
637+
638+
tab._connection_handler.execute_command.assert_called_once()
639+
640+
@pytest.mark.asyncio
641+
async def test_execute_script_already_wrapped_function(self, tab):
642+
"""Test execute_script with already wrapped function."""
643+
tab._connection_handler.execute_command.return_value = {
644+
'result': {'result': {'value': 'Already wrapped'}}
645+
}
646+
647+
# Script already wrapped in function should not be wrapped again
648+
script = 'function() { console.log("test"); return "done"; }'
649+
result = await tab.execute_script(script)
536650

537651
tab._connection_handler.execute_command.assert_called_once()
538652

tests/test_utils.py

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
from unittest.mock import patch
77

88
from pydoll import exceptions
9-
from pydoll.utils import decode_base64_to_bytes, get_browser_ws_address, validate_browser_paths
9+
from pydoll.utils import (
10+
clean_script_for_analysis,
11+
decode_base64_to_bytes,
12+
get_browser_ws_address,
13+
has_return_outside_function,
14+
is_script_already_function,
15+
validate_browser_paths,
16+
)
1017

1118

1219
class TestUtils:
@@ -228,3 +235,169 @@ def test_validate_browser_paths_mixed_valid_invalid(self):
228235
result = validate_browser_paths(paths)
229236
assert result == valid_path
230237

238+
239+
class TestDecodeBase64ToBytes:
240+
"""Test decode_base64_to_bytes function."""
241+
242+
def test_decode_base64_to_bytes_valid_input(self):
243+
"""Test decoding valid base64 string."""
244+
base64_string = 'SGVsbG8gV29ybGQ=' # "Hello World" in base64
245+
result = decode_base64_to_bytes(base64_string)
246+
assert result == b'Hello World'
247+
248+
def test_decode_base64_to_bytes_empty_string(self):
249+
"""Test decoding empty base64 string."""
250+
result = decode_base64_to_bytes('')
251+
assert result == b''
252+
253+
254+
class TestValidateBrowserPaths:
255+
"""Test validate_browser_paths function."""
256+
257+
def test_validate_browser_paths_valid_path(self, tmp_path):
258+
"""Test with valid executable path."""
259+
# Create a temporary executable file
260+
executable = tmp_path / "browser"
261+
executable.write_text("#!/bin/bash\necho 'browser'")
262+
executable.chmod(0o755)
263+
264+
result = validate_browser_paths([str(executable)])
265+
assert result == str(executable)
266+
267+
def test_validate_browser_paths_invalid_paths(self):
268+
"""Test with invalid paths."""
269+
from pydoll.exceptions import InvalidBrowserPath
270+
271+
with pytest.raises(InvalidBrowserPath):
272+
validate_browser_paths(['/nonexistent/path', '/another/invalid/path'])
273+
274+
275+
class TestScriptAnalysisFunctions:
276+
"""Test JavaScript script analysis functions."""
277+
278+
def test_clean_script_for_analysis_removes_comments(self):
279+
"""Test that comments are removed from script."""
280+
script = '''
281+
// This is a line comment
282+
var x = 5;
283+
/* This is a block comment */
284+
return x;
285+
'''
286+
287+
result = clean_script_for_analysis(script)
288+
289+
assert '// This is a line comment' not in result
290+
assert '/* This is a block comment */' not in result
291+
assert 'var x = 5;' in result
292+
assert 'return x;' in result
293+
294+
def test_clean_script_for_analysis_removes_strings(self):
295+
"""Test that string literals are removed from script."""
296+
script = '''
297+
var message = "This string contains return statement";
298+
var another = 'Another string with return';
299+
var template = `Template literal with return`;
300+
return "actual return";
301+
'''
302+
303+
result = clean_script_for_analysis(script)
304+
305+
assert 'This string contains return statement' not in result
306+
assert 'Another string with return' not in result
307+
assert 'Template literal with return' not in result
308+
assert 'return ""' in result # String replaced with empty quotes
309+
310+
def test_is_script_already_function_regular_function(self):
311+
"""Test detection of regular function declaration."""
312+
script = 'function() { console.log("test"); }'
313+
assert is_script_already_function(script) is True
314+
315+
def test_is_script_already_function_arrow_function(self):
316+
"""Test detection of arrow function."""
317+
script = '() => { console.log("test"); }'
318+
assert is_script_already_function(script) is True
319+
320+
def test_is_script_already_function_with_parameters(self):
321+
"""Test detection of function with parameters."""
322+
script = 'function(a, b) { return a + b; }'
323+
assert is_script_already_function(script) is True
324+
325+
def test_is_script_already_function_not_function(self):
326+
"""Test detection when script is not a function."""
327+
script = 'console.log("test"); return "value";'
328+
assert is_script_already_function(script) is False
329+
330+
def test_is_script_already_function_with_whitespace(self):
331+
"""Test detection with leading/trailing whitespace."""
332+
script = ' function() { test(); } '
333+
assert is_script_already_function(script) is True
334+
335+
def test_has_return_outside_function_simple_return(self):
336+
"""Test detection of simple return statement."""
337+
script = 'return document.title;'
338+
assert has_return_outside_function(script) is True
339+
340+
def test_has_return_outside_function_no_return(self):
341+
"""Test when script has no return statement."""
342+
script = 'console.log("test"); var x = 5;'
343+
assert has_return_outside_function(script) is False
344+
345+
def test_has_return_outside_function_return_inside_function(self):
346+
"""Test when return is inside a function."""
347+
script = '''
348+
function getTitle() {
349+
return document.title;
350+
}
351+
getTitle();
352+
'''
353+
assert has_return_outside_function(script) is False
354+
355+
def test_has_return_outside_function_mixed_returns(self):
356+
"""Test with both inside and outside returns."""
357+
script = '''
358+
function inner() {
359+
return "inner";
360+
}
361+
return "outer";
362+
'''
363+
assert has_return_outside_function(script) is True
364+
365+
def test_has_return_outside_function_already_function(self):
366+
"""Test when script is already a function."""
367+
script = 'function() { return "test"; }'
368+
assert has_return_outside_function(script) is False
369+
370+
def test_has_return_outside_function_with_comments(self):
371+
"""Test with comments containing 'return'."""
372+
script = '''
373+
// This comment has return in it
374+
var message = "This string has return in it";
375+
/* This block comment also has return */
376+
return "actual return";
377+
'''
378+
assert has_return_outside_function(script) is True
379+
380+
def test_has_return_outside_function_nested_braces(self):
381+
"""Test with nested braces and complex structure."""
382+
script = '''
383+
if (true) {
384+
var obj = {
385+
method: function() {
386+
return "nested";
387+
}
388+
};
389+
}
390+
return "outside";
391+
'''
392+
assert has_return_outside_function(script) is True
393+
394+
def test_has_return_outside_function_arrow_function(self):
395+
"""Test with arrow function containing return."""
396+
script = '''
397+
var func = () => {
398+
return "arrow";
399+
};
400+
func();
401+
'''
402+
assert has_return_outside_function(script) is False
403+

0 commit comments

Comments
 (0)