diff --git a/examples/cdp_mode/playwright/raw_cf_cap_sync.py b/examples/cdp_mode/playwright/raw_cf_cap_sync.py new file mode 100644 index 00000000000..8bfa09269b1 --- /dev/null +++ b/examples/cdp_mode/playwright/raw_cf_cap_sync.py @@ -0,0 +1,14 @@ +from playwright.sync_api import sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(locale="en") +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + context = browser.contexts[0] + page = context.pages[0] + page.goto("https://www.cloudflare.com/login") + sb.sleep(3) + sb.solve_captcha() + sb.sleep(3) diff --git a/examples/cdp_mode/playwright/raw_nike_sync.py b/examples/cdp_mode/playwright/raw_nike_sync.py new file mode 100644 index 00000000000..5cb04ba8cd8 --- /dev/null +++ b/examples/cdp_mode/playwright/raw_nike_sync.py @@ -0,0 +1,22 @@ +from playwright.sync_api import sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + context = browser.contexts[0] + page = context.pages[0] + page.goto("https://www.nike.com/") + page.click('[data-testid="user-tools-container"] search') + search = "Pegasus" + page.fill('input[type="search"]', search) + sb.sleep(4) + details = 'ul[data-testid*="products"] figure .details' + items = page.locator(details) + if items: + print('**** Found results for "%s": ****' % search) + for i in range(items.count()): + item = items.nth(i) + print(item.inner_text()) diff --git a/examples/cdp_mode/playwright/raw_nordstrom_sync.py b/examples/cdp_mode/playwright/raw_nordstrom_sync.py new file mode 100644 index 00000000000..3e9741530d0 --- /dev/null +++ b/examples/cdp_mode/playwright/raw_nordstrom_sync.py @@ -0,0 +1,32 @@ +from playwright.sync_api import sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(locale="en") +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + context = browser.contexts[0] + page = context.pages[0] + page.goto("https://www.nordstrom.com/") + sb.sleep(2) + page.click("input#keyword-search-input") + sb.sleep(0.8) + search = "cocktail dresses for women teal" + sb.press_keys("input#keyword-search-input", search + "\n") + sb.sleep(2.2) + for i in range(17): + sb.scroll_down(16) + sb.sleep(0.14) + print('*** Nordstrom Search for "%s":' % search) + unique_item_text = [] + items = sb.find_elements("article") + for item in items: + description = item.querySelector("article h3") + if description and description.text not in unique_item_text: + unique_item_text.append(description.text) + price_text = "" + price = item.querySelector('div div span[aria-hidden="true"]') + if price: + price_text = price.text + print("* %s (%s)" % (description.text, price_text)) diff --git a/examples/cdp_mode/raw_canvas.py b/examples/cdp_mode/raw_canvas.py index 50a5ad20bd4..7ec4380de6e 100644 --- a/examples/cdp_mode/raw_canvas.py +++ b/examples/cdp_mode/raw_canvas.py @@ -4,7 +4,7 @@ def get_canvas_pixel_colors_at_top_left(sb): # Return the RGB colors of the canvas's top left pixel - color = sb.cdp.evaluate( + color = sb.evaluate( "document.querySelector('canvas').getContext('2d')" ".getImageData(%s,%s,1,1).data;" % (0, 0) ) @@ -19,7 +19,7 @@ def get_canvas_pixel_colors_at_top_left(sb): sb.highlight("canvas") rgb = get_canvas_pixel_colors_at_top_left(sb) sb.assert_equal(rgb, [221, 242, 231]) # Looks greenish - sb.cdp.click_with_offset("canvas", 500, 350) + sb.click_with_offset("canvas", 500, 350) sb.highlight("canvas", loops=5) rgb = get_canvas_pixel_colors_at_top_left(sb) sb.assert_equal(rgb, [39, 43, 56]) # Blue by hamburger @@ -29,7 +29,7 @@ def get_canvas_pixel_colors_at_top_left(sb): url = "https://seleniumbase.io/other/canvas" sb.activate_cdp_mode(url) sb.assert_title_contains("Canvas") - sb.cdp.click_with_offset("canvas", 0, 0, center=True) + sb.click_with_offset("canvas", 0, 0, center=True) sb.sleep(1) sb.uc_gui_press_key("ENTER") sb.sleep(0.5) diff --git a/examples/cdp_mode/raw_ralphlauren.py b/examples/cdp_mode/raw_ralphlauren.py index 99da77ad065..7139759a8df 100644 --- a/examples/cdp_mode/raw_ralphlauren.py +++ b/examples/cdp_mode/raw_ralphlauren.py @@ -6,7 +6,7 @@ sb.open(url) sb.sleep(1.2) if not sb.is_element_present('[title="Locate Stores"]'): - sb.cdp.evaluate("window.location.reload();") + sb.evaluate("window.location.reload();") sb.sleep(1.2) category = "women" search = "Dresses" diff --git a/examples/cdp_mode/raw_totalwine.py b/examples/cdp_mode/raw_totalwine.py index 1292be3ad52..e1afd89976a 100644 --- a/examples/cdp_mode/raw_totalwine.py +++ b/examples/cdp_mode/raw_totalwine.py @@ -8,7 +8,7 @@ search_box = 'input[data-at="header-search-text"]' search = "The Land by Psagot Cabernet" if not sb.is_element_present(search_box): - sb.cdp.evaluate("window.location.reload();") + sb.evaluate("window.location.reload();") sb.sleep(1.8) sb.click_if_visible("#onetrust-close-btn-container button") sb.sleep(0.5) diff --git a/examples/cdp_mode/raw_zoro.py b/examples/cdp_mode/raw_zoro.py index df673467816..035c2412622 100644 --- a/examples/cdp_mode/raw_zoro.py +++ b/examples/cdp_mode/raw_zoro.py @@ -9,7 +9,7 @@ search = "Flir Thermal Camera" required_text = "Camera" if not sb.is_element_present(search_box): - sb.cdp.evaluate("window.location.reload();") + sb.evaluate("window.location.reload();") sb.sleep(1.2) sb.click(search_box) sb.sleep(1.2) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index cfeec754250..151e843352e 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -144,6 +144,7 @@ self.load_html_string(html_string, new_page=True) self.set_content(html_string, new_page=False) self.load_html_file(html_file, new_page=True) self.open_html_file(html_file) +self.evaluate(expression) self.execute_script(script, *args, **kwargs) self.execute_cdp_cmd(script, *args, **kwargs) self.execute_async_script(script, timeout=None) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 0513ea6fd70..0ca6dee91b8 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.45.7" +__version__ = "4.45.8" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index c604515d416..8af9f901aff 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -6011,8 +6011,18 @@ def get_local_driver( time.sleep(0.003) driver.switch_to.window(driver.window_handles[0]) time.sleep(0.003) - driver.connect() - time.sleep(0.003) + # seleniumbase/SeleniumBase/discussions/4190 + if getattr(sb_config, "skip_133_patch", None): + # To skip the connect() patch for Chrome 133+: + # from seleniumbase import config as sb_config + # sb_config.skip_133_patch = True + # (Do the above before launching the browser.) + pass + else: + # This fixes an issue on Chrome 133+ + # (Some people might not need it though.) + driver.connect() + time.sleep(0.003) if mobile_emulator: uc_metrics = {} if ( diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 49462823a0c..74b86679a85 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -770,7 +770,9 @@ def click(self, selector, timeout=None): if tag_name: tag_name = tag_name.lower().strip() if ( - tag_name in ["a", "button", "canvas", "div", "input", "li", "span"] + tag_name in [ + "a", "button", "canvas", "div", "input", "li", "span", "label" + ] and "contains(" not in selector ): try: diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 3b34af66930..fd361079234 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3446,6 +3446,46 @@ def open_html_file(self, html_file): file_path = os.path.join(abs_path, html_file) self.open("file://" + file_path) + def evaluate(self, expression): + """Run a JavaScript expression and return the result.""" + self.__check_scope() + if self.__is_cdp_swap_needed(): + return self.cdp.evaluate(expression) + self._check_browser() + original_expression = expression + expression = expression.strip() + exp_list = expression.split("\n") + if exp_list and exp_list[-1].strip().startswith("return "): + expression = ( + "\n".join(exp_list[0:-1]) + "\n" + + exp_list[-1].strip()[len("return "):] + ).strip() + evaluation = self.driver.execute_cdp_cmd( + "Runtime.evaluate", + { + "expression": expression + }, + ) + if "value" in evaluation["result"]: + return evaluation["result"]["value"] + elif evaluation["result"]["type"] == "undefined": + return None + elif "exceptionDetails" in evaluation: + raise Exception(evaluation["result"]["description"], expression) + elif evaluation["result"]["type"] == "object": + if "return " not in original_expression: + expression = "return " + original_expression.strip() + # Need to use execute_script() to return a WebDriver object. + # If this causes duplicate evaluation, don't use evaluate(). + return self.execute_script(expression) + elif evaluation["result"]["type"] == "function": + return {} # This is what sb.cdp.evaluate returns + elif "description" in evaluation["result"]: + # At this point, the description is the exception + raise Exception(evaluation["result"]["description"], expression) + else: # Possibly an unhandled case if reached + return None + def execute_script(self, script, *args, **kwargs): self.__check_scope() if self.__is_cdp_swap_needed(): diff --git a/seleniumbase/undetected/cdp_driver/connection.py b/seleniumbase/undetected/cdp_driver/connection.py index 3d192749112..4a97a31a5fd 100644 --- a/seleniumbase/undetected/cdp_driver/connection.py +++ b/seleniumbase/undetected/cdp_driver/connection.py @@ -639,12 +639,14 @@ async def listener_loop(self): or inspect.iscoroutine(callback) ): try: - asyncio.create_task(callback(event, self)) + asyncio.create_task( + callback(event, self.connection) + ) except TypeError: asyncio.create_task(callback(event)) else: try: - callback(event, self) + callback(event, self.connection) except TypeError: callback(event) except Exception as e: diff --git a/setup.py b/setup.py index febeb93bcec..178a6669aa1 100755 --- a/setup.py +++ b/setup.py @@ -259,7 +259,7 @@ # (An optional library for parsing PDF files.) "pdfminer": [ 'pdfminer.six==20251107;python_version<"3.10"', - 'pdfminer.six==20251229;python_version>="3.10"', + 'pdfminer.six==20251230;python_version>="3.10"', 'cryptography==46.0.3', 'cffi==2.0.0', 'pycparser==2.23',