Path: blob/trunk/py/test/selenium/webdriver/common/bidi_script_tests.py
1865 views
# Licensed to the Software Freedom Conservancy (SFC) under one1# or more contributor license agreements. See the NOTICE file2# distributed with this work for additional information3# regarding copyright ownership. The SFC licenses this file4# to you under the Apache License, Version 2.0 (the5# "License"); you may not use this file except in compliance6# with the License. You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing,11# software distributed under the License is distributed on an12# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13# KIND, either express or implied. See the License for the14# specific language governing permissions and limitations15# under the License.1617import pytest1819from selenium.webdriver.common.bidi.log import LogLevel20from selenium.webdriver.common.bidi.script import RealmType, ResultOwnership21from selenium.webdriver.common.by import By22from selenium.webdriver.support.ui import WebDriverWait232425def has_shadow_root(node):26if isinstance(node, dict):27shadow_root = node.get("shadowRoot")28if shadow_root and isinstance(shadow_root, dict):29return True3031children = node.get("children", [])32for child in children:33if "value" in child and has_shadow_root(child["value"]):34return True3536return False373839def test_logs_console_messages(driver, pages):40pages.load("bidi/logEntryAdded.html")4142log_entries = []43driver.script.add_console_message_handler(log_entries.append)4445driver.find_element(By.ID, "jsException").click()46driver.find_element(By.ID, "consoleLog").click()4748WebDriverWait(driver, 5).until(lambda _: log_entries)4950log_entry = log_entries[0]51assert log_entry.level == LogLevel.INFO52assert log_entry.method == "log"53assert log_entry.text == "Hello, world!"54assert log_entry.type_ == "console"555657def test_logs_console_errors(driver, pages):58pages.load("bidi/logEntryAdded.html")59log_entries = []6061def log_error(entry):62if entry.level == LogLevel.ERROR:63log_entries.append(entry)6465driver.script.add_console_message_handler(log_error)6667driver.find_element(By.ID, "consoleLog").click()68driver.find_element(By.ID, "consoleError").click()6970WebDriverWait(driver, 5).until(lambda _: log_entries)7172assert len(log_entries) == 17374log_entry = log_entries[0]75assert log_entry.level == LogLevel.ERROR76assert log_entry.method == "error"77assert log_entry.text == "I am console error"78assert log_entry.type_ == "console"798081def test_logs_multiple_console_messages(driver, pages):82pages.load("bidi/logEntryAdded.html")8384log_entries = []85driver.script.add_console_message_handler(log_entries.append)86driver.script.add_console_message_handler(log_entries.append)8788driver.find_element(By.ID, "jsException").click()89driver.find_element(By.ID, "consoleLog").click()9091WebDriverWait(driver, 5).until(lambda _: len(log_entries) > 1)92assert len(log_entries) == 2939495def test_removes_console_message_handler(driver, pages):96pages.load("bidi/logEntryAdded.html")9798log_entries1 = []99log_entries2 = []100101id = driver.script.add_console_message_handler(log_entries1.append)102driver.script.add_console_message_handler(log_entries2.append)103104driver.find_element(By.ID, "consoleLog").click()105WebDriverWait(driver, 5).until(lambda _: len(log_entries1) and len(log_entries2))106107driver.script.remove_console_message_handler(id)108driver.find_element(By.ID, "consoleLog").click()109110WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2)111assert len(log_entries1) == 1112113114def test_javascript_error_messages(driver, pages):115pages.load("bidi/logEntryAdded.html")116117log_entries = []118driver.script.add_javascript_error_handler(log_entries.append)119120driver.find_element(By.ID, "jsException").click()121WebDriverWait(driver, 5).until(lambda _: log_entries)122123log_entry = log_entries[0]124assert log_entry.text == "Error: Not working"125assert log_entry.level == LogLevel.ERROR126assert log_entry.type_ == "javascript"127128129def test_removes_javascript_message_handler(driver, pages):130pages.load("bidi/logEntryAdded.html")131132log_entries1 = []133log_entries2 = []134135id = driver.script.add_javascript_error_handler(log_entries1.append)136driver.script.add_javascript_error_handler(log_entries2.append)137138driver.find_element(By.ID, "jsException").click()139WebDriverWait(driver, 5).until(lambda _: len(log_entries1) and len(log_entries2))140141driver.script.remove_javascript_error_handler(id)142driver.find_element(By.ID, "jsException").click()143144WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2)145assert len(log_entries1) == 1146147148def test_add_preload_script(driver, pages):149"""Test adding a preload script."""150function_declaration = "() => { window.preloadExecuted = true; }"151152script_id = driver.script._add_preload_script(function_declaration)153assert script_id is not None154assert isinstance(script_id, str)155156# Navigate to a page to trigger the preload script157pages.load("blank.html")158159# Check if the preload script was executed160result = driver.script._evaluate(161"window.preloadExecuted", {"context": driver.current_window_handle}, await_promise=False162)163assert result.result["value"] is True164165166def test_add_preload_script_with_arguments(driver, pages):167"""Test adding a preload script with channel arguments."""168function_declaration = "(channelFunc) => { channelFunc('test_value'); window.preloadValue = 'received'; }"169170arguments = [{"type": "channel", "value": {"channel": "test-channel", "ownership": "root"}}]171172script_id = driver.script._add_preload_script(function_declaration, arguments=arguments)173assert script_id is not None174175pages.load("blank.html")176177result = driver.script._evaluate(178"window.preloadValue", {"context": driver.current_window_handle}, await_promise=False179)180assert result.result["value"] == "received"181182183def test_add_preload_script_with_contexts(driver, pages):184"""Test adding a preload script with specific contexts."""185function_declaration = "() => { window.contextSpecific = true; }"186contexts = [driver.current_window_handle]187188script_id = driver.script._add_preload_script(function_declaration, contexts=contexts)189assert script_id is not None190191pages.load("blank.html")192193result = driver.script._evaluate(194"window.contextSpecific", {"context": driver.current_window_handle}, await_promise=False195)196assert result.result["value"] is True197198199def test_add_preload_script_with_user_contexts(driver, pages):200"""Test adding a preload script with user contexts."""201function_declaration = "() => { window.contextSpecific = true; }"202user_context = driver.browser.create_user_context()203204context1 = driver.browsing_context.create(type="window", user_context=user_context)205driver.switch_to.window(context1)206207user_contexts = [user_context]208209script_id = driver.script._add_preload_script(function_declaration, user_contexts=user_contexts)210assert script_id is not None211212pages.load("blank.html")213214result = driver.script._evaluate(215"window.contextSpecific", {"context": driver.current_window_handle}, await_promise=False216)217assert result.result["value"] is True218219220def test_add_preload_script_with_sandbox(driver, pages):221"""Test adding a preload script with sandbox."""222function_declaration = "() => { window.sandboxScript = true; }"223224script_id = driver.script._add_preload_script(function_declaration, sandbox="test-sandbox")225assert script_id is not None226227pages.load("blank.html")228229# calling evaluate without sandbox should return undefined230result = driver.script._evaluate(231"window.sandboxScript", {"context": driver.current_window_handle}, await_promise=False232)233assert result.result["type"] == "undefined"234235# calling evaluate within the sandbox should return True236result = driver.script._evaluate(237"window.sandboxScript",238{"context": driver.current_window_handle, "sandbox": "test-sandbox"},239await_promise=False,240)241assert result.result["value"] is True242243244def test_add_preload_script_invalid_arguments(driver):245"""Test that providing both contexts and user_contexts raises an error."""246function_declaration = "() => {}"247248with pytest.raises(ValueError, match="Cannot specify both contexts and user_contexts"):249driver.script._add_preload_script(function_declaration, contexts=["context1"], user_contexts=["user1"])250251252def test_remove_preload_script(driver, pages):253"""Test removing a preload script."""254function_declaration = "() => { window.removableScript = true; }"255256script_id = driver.script._add_preload_script(function_declaration)257driver.script._remove_preload_script(script_id=script_id)258259# Navigate to a page after removing the script260pages.load("blank.html")261262# The script should not have executed263result = driver.script._evaluate(264"typeof window.removableScript", {"context": driver.current_window_handle}, await_promise=False265)266assert result.result["value"] == "undefined"267268269def test_evaluate_expression(driver, pages):270"""Test evaluating a simple expression."""271pages.load("blank.html")272273result = driver.script._evaluate("1 + 2", {"context": driver.current_window_handle}, await_promise=False)274275assert result.realm is not None276assert result.result["type"] == "number"277assert result.result["value"] == 3278assert result.exception_details is None279280281def test_evaluate_with_await_promise(driver, pages):282"""Test evaluating an expression that returns a promise."""283pages.load("blank.html")284285result = driver.script._evaluate(286"Promise.resolve(42)", {"context": driver.current_window_handle}, await_promise=True287)288289assert result.result["type"] == "number"290assert result.result["value"] == 42291292293def test_evaluate_with_exception(driver, pages):294"""Test evaluating an expression that throws an exception."""295pages.load("blank.html")296297result = driver.script._evaluate(298"throw new Error('Test error')", {"context": driver.current_window_handle}, await_promise=False299)300301assert result.exception_details is not None302assert "Test error" in str(result.exception_details)303304305def test_evaluate_with_result_ownership(driver, pages):306"""Test evaluating with different result ownership settings."""307pages.load("blank.html")308309# Test with ROOT ownership310result = driver.script._evaluate(311"({ test: 'value' })",312{"context": driver.current_window_handle},313await_promise=False,314result_ownership=ResultOwnership.ROOT,315)316317# ROOT result ownership should return a handle318assert "handle" in result.result319320# Test with NONE ownership321result = driver.script._evaluate(322"({ test: 'value' })",323{"context": driver.current_window_handle},324await_promise=False,325result_ownership=ResultOwnership.NONE,326)327328assert "handle" not in result.result329assert result.result is not None330331332def test_evaluate_with_serialization_options(driver, pages):333"""Test evaluating with serialization options."""334pages.load("shadowRootPage.html")335336serialization_options = {"maxDomDepth": 2, "maxObjectDepth": 2, "includeShadowTree": "all"}337338result = driver.script._evaluate(339"document.body",340{"context": driver.current_window_handle},341await_promise=False,342serialization_options=serialization_options,343)344root_node = result.result["value"]345346# maxDomDepth will contain a children property347assert "children" in result.result["value"]348# the page will have atleast one shadow root349assert has_shadow_root(root_node)350351352def test_evaluate_with_user_activation(driver, pages):353"""Test evaluating with user activation."""354pages.load("blank.html")355356result = driver.script._evaluate(357"navigator.userActivation ? navigator.userActivation.isActive : false",358{"context": driver.current_window_handle},359await_promise=False,360user_activation=True,361)362363# the value should be True if user activation is active364assert result.result["value"] is True365366367def test_call_function(driver, pages):368"""Test calling a function."""369pages.load("blank.html")370371result = driver.script._call_function(372"(a, b) => a + b",373await_promise=False,374target={"context": driver.current_window_handle},375arguments=[{"type": "number", "value": 5}, {"type": "number", "value": 3}],376)377378assert result.result["type"] == "number"379assert result.result["value"] == 8380381382def test_call_function_with_this(driver, pages):383"""Test calling a function with a specific 'this' value."""384pages.load("blank.html")385386# First set up an object387driver.script._evaluate(388"window.testObj = { value: 10 }", {"context": driver.current_window_handle}, await_promise=False389)390391result = driver.script._call_function(392"function() { return this.value; }",393await_promise=False,394target={"context": driver.current_window_handle},395this={"type": "object", "value": [["value", {"type": "number", "value": 20}]]},396)397398assert result.result["type"] == "number"399assert result.result["value"] == 20400401402def test_call_function_with_user_activation(driver, pages):403"""Test calling a function with user activation."""404pages.load("blank.html")405406result = driver.script._call_function(407"() => navigator.userActivation ? navigator.userActivation.isActive : false",408await_promise=False,409target={"context": driver.current_window_handle},410user_activation=True,411)412413# the value should be True if user activation is active414assert result.result["value"] is True415416417def test_call_function_with_serialization_options(driver, pages):418"""Test calling a function with serialization options."""419pages.load("shadowRootPage.html")420421serialization_options = {"maxDomDepth": 2, "maxObjectDepth": 2, "includeShadowTree": "all"}422423result = driver.script._call_function(424"() => document.body",425await_promise=False,426target={"context": driver.current_window_handle},427serialization_options=serialization_options,428)429430root_node = result.result["value"]431432# maxDomDepth will contain a children property433assert "children" in result.result["value"]434# the page will have atleast one shadow root435assert has_shadow_root(root_node)436437438def test_call_function_with_exception(driver, pages):439"""Test calling a function that throws an exception."""440pages.load("blank.html")441442result = driver.script._call_function(443"() => { throw new Error('Function error'); }",444await_promise=False,445target={"context": driver.current_window_handle},446)447448assert result.exception_details is not None449assert "Function error" in str(result.exception_details)450451452def test_call_function_with_await_promise(driver, pages):453"""Test calling a function that returns a promise."""454pages.load("blank.html")455456result = driver.script._call_function(457"() => Promise.resolve('async result')", await_promise=True, target={"context": driver.current_window_handle}458)459460assert result.result["type"] == "string"461assert result.result["value"] == "async result"462463464def test_call_function_with_result_ownership(driver, pages):465"""Test calling a function with different result ownership settings."""466pages.load("blank.html")467468# Call a function that returns an object with ownership "root"469result = driver.script._call_function(470"function() { return { greet: 'Hi', number: 42 }; }",471await_promise=False,472target={"context": driver.current_window_handle},473result_ownership="root",474)475476# Verify that a handle is returned477assert result.result["type"] == "object"478assert "handle" in result.result479handle = result.result["handle"]480481# Use the handle in another function call482result2 = driver.script._call_function(483"function() { return this.number + 1; }",484await_promise=False,485target={"context": driver.current_window_handle},486this={"handle": handle},487)488489assert result2.result["type"] == "number"490assert result2.result["value"] == 43491492493def test_get_realms(driver, pages):494"""Test getting all realms."""495pages.load("blank.html")496497realms = driver.script._get_realms()498499assert len(realms) > 0500assert all(hasattr(realm, "realm") for realm in realms)501assert all(hasattr(realm, "origin") for realm in realms)502assert all(hasattr(realm, "type") for realm in realms)503504505def test_get_realms_filtered_by_context(driver, pages):506"""Test getting realms filtered by context."""507pages.load("blank.html")508509realms = driver.script._get_realms(context=driver.current_window_handle)510511assert len(realms) > 0512# All realms should be associated with the specified context513for realm in realms:514if realm.context is not None:515assert realm.context == driver.current_window_handle516517518def test_get_realms_filtered_by_type(driver, pages):519"""Test getting realms filtered by type."""520pages.load("blank.html")521522realms = driver.script._get_realms(type=RealmType.WINDOW)523524assert len(realms) > 0525# All realms should be of the WINDOW type526for realm in realms:527assert realm.type == RealmType.WINDOW528529530def test_disown_handles(driver, pages):531"""Test disowning handles."""532pages.load("blank.html")533534# Create an object with root ownership (this will return a handle)535result = driver.script._evaluate(536"({foo: 'bar'})", target={"context": driver.current_window_handle}, await_promise=False, result_ownership="root"537)538539handle = result.result["handle"]540assert handle is not None541542# Use the handle in a function call (this should succeed)543result_before = driver.script._call_function(544"function(obj) { return obj.foo; }",545await_promise=False,546target={"context": driver.current_window_handle},547arguments=[{"handle": handle}],548)549550assert result_before.result["value"] == "bar"551552# Disown the handle553driver.script._disown(handles=[handle], target={"context": driver.current_window_handle})554555# Try using the disowned handle (this should fail)556with pytest.raises(Exception):557driver.script._call_function(558"function(obj) { return obj.foo; }",559await_promise=False,560target={"context": driver.current_window_handle},561arguments=[{"handle": handle}],562)563564565# Tests for high-level SCRIPT API commands - pin, unpin, and execute566567568def test_pin_script(driver, pages):569"""Test pinning a script."""570function_declaration = "() => { window.pinnedScriptExecuted = 'yes'; }"571572script_id = driver.script.pin(function_declaration)573assert script_id is not None574assert isinstance(script_id, str)575576pages.load("blank.html")577578result = driver.script.execute("() => window.pinnedScriptExecuted")579assert result["value"] == "yes"580581582def test_unpin_script(driver, pages):583"""Test unpinning a script."""584function_declaration = "() => { window.unpinnableScript = 'executed'; }"585586script_id = driver.script.pin(function_declaration)587driver.script.unpin(script_id)588589pages.load("blank.html")590591result = driver.script.execute("() => typeof window.unpinnableScript")592assert result["value"] == "undefined"593594595def test_execute_script_with_null_argument(driver, pages):596"""Test executing script with undefined argument."""597pages.load("blank.html")598599result = driver.script.execute(600"""(arg) => {601if(arg!==null)602throw Error("Argument should be null, but was "+arg);603return arg;604}""",605None,606)607608assert result["type"] == "null"609610611def test_execute_script_with_number_argument(driver, pages):612"""Test executing script with number argument."""613pages.load("blank.html")614615result = driver.script.execute(616"""(arg) => {617if(arg!==1.4)618throw Error("Argument should be 1.4, but was "+arg);619return arg;620}""",6211.4,622)623624assert result["type"] == "number"625assert result["value"] == 1.4626627628def test_execute_script_with_nan(driver, pages):629"""Test executing script with NaN argument."""630pages.load("blank.html")631632result = driver.script.execute(633"""(arg) => {634if(!Number.isNaN(arg))635throw Error("Argument should be NaN, but was "+arg);636return arg;637}""",638float("nan"),639)640641assert result["type"] == "number"642assert result["value"] == "NaN"643644645def test_execute_script_with_inf(driver, pages):646"""Test executing script with number argument."""647pages.load("blank.html")648649result = driver.script.execute(650"""(arg) => {651if(arg!==Infinity)652throw Error("Argument should be Infinity, but was "+arg);653return arg;654}""",655float("inf"),656)657658assert result["type"] == "number"659assert result["value"] == "Infinity"660661662def test_execute_script_with_minus_inf(driver, pages):663"""Test executing script with number argument."""664pages.load("blank.html")665666result = driver.script.execute(667"""(arg) => {668if(arg!==-Infinity)669throw Error("Argument should be -Infinity, but was "+arg);670return arg;671}""",672float("-inf"),673)674675assert result["type"] == "number"676assert result["value"] == "-Infinity"677678679def test_execute_script_with_bigint_argument(driver, pages):680"""Test executing script with BigInt argument."""681pages.load("blank.html")682683# Use a large integer that exceeds JavaScript safe integer limit684large_int = 9007199254740992685result = driver.script.execute(686"""(arg) => {687if(arg !== 9007199254740992n)688throw Error("Argument should be 9007199254740992n (BigInt), but was "+arg+" (type: "+typeof arg+")");689return arg;690}""",691large_int,692)693694assert result["type"] == "bigint"695assert result["value"] == str(large_int)696697698def test_execute_script_with_boolean_argument(driver, pages):699"""Test executing script with boolean argument."""700pages.load("blank.html")701702result = driver.script.execute(703"""(arg) => {704if(arg!==true)705throw Error("Argument should be true, but was "+arg);706return arg;707}""",708True,709)710711assert result["type"] == "boolean"712assert result["value"] is True713714715def test_execute_script_with_string_argument(driver, pages):716"""Test executing script with string argument."""717pages.load("blank.html")718719result = driver.script.execute(720"""(arg) => {721if(arg!=="hello world")722throw Error("Argument should be 'hello world', but was "+arg);723return arg;724}""",725"hello world",726)727728assert result["type"] == "string"729assert result["value"] == "hello world"730731732def test_execute_script_with_date_argument(driver, pages):733"""Test executing script with date argument."""734import datetime735736pages.load("blank.html")737738date = datetime.datetime(2023, 12, 25, 10, 30, 45)739result = driver.script.execute(740"""(arg) => {741if(!(arg instanceof Date))742throw Error("Argument type should be Date, but was "+743Object.prototype.toString.call(arg));744if(arg.getFullYear() !== 2023)745throw Error("Year should be 2023, but was "+arg.getFullYear());746return arg;747}""",748date,749)750751assert result["type"] == "date"752assert "2023-12-25T10:30:45" in result["value"]753754755def test_execute_script_with_array_argument(driver, pages):756"""Test executing script with array argument."""757pages.load("blank.html")758759test_list = [1, 2, 3]760761result = driver.script.execute(762"""(arg) => {763if(!(arg instanceof Array))764throw Error("Argument type should be Array, but was "+765Object.prototype.toString.call(arg));766if(arg.length !== 3)767throw Error("Array should have 3 elements, but had "+arg.length);768return arg;769}""",770test_list,771)772773assert result["type"] == "array"774values = result["value"]775assert len(values) == 3776777778def test_execute_script_with_multiple_arguments(driver, pages):779"""Test executing script with multiple arguments."""780pages.load("blank.html")781782result = driver.script.execute(783"""(a, b, c) => {784if(a !== 1) throw Error("First arg should be 1");785if(b !== "test") throw Error("Second arg should be 'test'");786if(c !== true) throw Error("Third arg should be true");787return a + b.length + (c ? 1 : 0);788}""",7891,790"test",791True,792)793794assert result["type"] == "number"795assert result["value"] == 6 # 1 + 4 + 1796797798def test_execute_script_returns_promise(driver, pages):799"""Test executing script that returns a promise."""800pages.load("blank.html")801802result = driver.script.execute(803"""() => {804return Promise.resolve("async result");805}""",806)807808assert result["type"] == "string"809assert result["value"] == "async result"810811812def test_execute_script_with_exception(driver, pages):813"""Test executing script that throws an exception."""814pages.load("blank.html")815816from selenium.common.exceptions import WebDriverException817818with pytest.raises(WebDriverException) as exc_info:819driver.script.execute(820"""() => {821throw new Error("Test error message");822}""",823)824825assert "Test error message" in str(exc_info.value)826827828def test_execute_script_accessing_dom(driver, pages):829"""Test executing script that accesses DOM elements."""830pages.load("formPage.html")831832result = driver.script.execute(833"""() => {834return document.title;835}""",836)837838assert result["type"] == "string"839assert result["value"] == "We Leave From Here"840841842def test_execute_script_with_nested_objects(driver, pages):843"""Test executing script with nested object arguments."""844pages.load("blank.html")845846nested_data = {847"user": {848"name": "John",849"age": 30,850"hobbies": ["reading", "coding"],851},852"settings": {"theme": "dark", "notifications": True},853}854855result = driver.script.execute(856"""(data) => {857return {858userName: data.user.name,859userAge: data.user.age,860hobbyCount: data.user.hobbies.length,861theme: data.settings.theme862};863}""",864nested_data,865)866867assert result["type"] == "object"868value_dict = {k: v["value"] for k, v in result["value"]}869assert value_dict["userName"] == "John"870assert value_dict["userAge"] == 30871assert value_dict["hobbyCount"] == 2872873874