diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index b08995fab0b..b98f8e68a6d 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -421,6 +421,7 @@ sb.cdp.clear_input(selector, timeout=None) sb.cdp.clear(selector, timeout=None) sb.cdp.submit(selector) sb.cdp.evaluate(expression) +sb.cdp.execute_script(expression) sb.cdp.js_dumps(obj_name) sb.cdp.maximize() sb.cdp.minimize() @@ -464,6 +465,9 @@ sb.cdp.get_attribute(selector, attribute) sb.cdp.get_element_html(selector) sb.cdp.get_mfa_code(totp_key=None) sb.cdp.enter_mfa_code(selector, totp_key=None, timeout=None) +sb.cdp.activate_messenger() +sb.cdp.set_messenger_theme(theme="default", location="default") +sb.cdp.post_message(message, duration=None, pause=True, style="info") sb.cdp.set_locale(locale) sb.cdp.set_local_storage_item(key, value) sb.cdp.set_session_storage_item(key, value) @@ -528,6 +532,8 @@ sb.cdp.scroll_to_top() sb.cdp.scroll_to_bottom() sb.cdp.scroll_up(amount=25) sb.cdp.scroll_down(amount=25) +sb.cdp.save_page_source(name, folder=None) +sb.cdp.save_as_html(name, folder=None) sb.cdp.save_screenshot(name, folder=None, selector=None) sb.cdp.print_to_pdf(name, folder=None) sb.cdp.save_as_pdf(name, folder=None) diff --git a/examples/cdp_mode/raw_ad_blocking.py b/examples/cdp_mode/raw_ad_blocking.py new file mode 100644 index 00000000000..8d6f8a09c34 --- /dev/null +++ b/examples/cdp_mode/raw_ad_blocking.py @@ -0,0 +1,31 @@ +import mycdp +from seleniumbase import decorators +from seleniumbase import sb_cdp + + +async def block_urls(tab): + await tab.send(mycdp.network.enable()) + await tab.send(mycdp.network.set_blocked_urls( + urls=[ + "*googlesyndication.com*", + "*googletagmanager.com*", + "*google-analytics.com*", + "*amazon-adsystem.com*", + "*adsafeprotected.com*", + "*doubleclick.net*", + "*fastclick.net*", + "*snigelweb.com*", + "*2mdn.net*", + ] + )) + +with decorators.print_runtime("raw_ad_blocking.py"): + sb = sb_cdp.Chrome() + loop = sb.get_event_loop() + loop.run_until_complete(block_urls(sb.get_active_tab())) + sb.open("https://www.w3schools.com/jquery/default.asp") + source = sb.get_page_source() + sb.assert_true("doubleclick.net" not in source) + sb.assert_true("google-analytics.com" not in source) + sb.post_message("Blocking was successful!") + sb.driver.quit() diff --git a/examples/cdp_mode/raw_cdp_copilot.py b/examples/cdp_mode/raw_cdp_copilot.py new file mode 100644 index 00000000000..40906354c60 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_copilot.py @@ -0,0 +1,34 @@ +from seleniumbase import sb_cdp + +url = "https://copilot.microsoft.com/" +sb = sb_cdp.Chrome(url, locale="en", guest=True) +textarea = "textarea#userInput" +sb.wait_for_element(textarea) +sb.sleep(1.5) +sb.click_if_visible('[aria-label="Dismiss"]') +sb.sleep(0.5) +sb.click('button[data-testid*="chat-mode-"]') +sb.sleep(1.1) +sb.click('button[title="Think Deeper"]') +sb.sleep(1.1) +query = "How to start automating with SeleniumBase?" +sb.press_keys(textarea, query) +sb.sleep(1.1) +sb.click('button[data-testid="submit-button"]') +sb.sleep(2.5) +sb.gui_click_captcha() +sb.sleep(2.5) +sb.gui_click_captcha() +sb.sleep(3.5) +stop_button = '[data-testid="stop-button"]' +thumbs_up = 'button[data-testid*="-thumbs-up-"]' +sb.wait_for_element_absent(stop_button, timeout=45) +sb.wait_for_element(thumbs_up, timeout=20) +sb.sleep(0.6) +sb.click('button[data-testid*="scroll-to-bottom"]') +sb.sleep(2.2) +folder = "downloaded_files" +file_name = "copilot_results.html" +sb.save_as_html(file_name, folder) +print('"./%s/%s" was saved!' % (folder, file_name)) +sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_hyatt.py b/examples/cdp_mode/raw_cdp_hyatt.py new file mode 100644 index 00000000000..43c1be21c53 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_hyatt.py @@ -0,0 +1,31 @@ +from seleniumbase import sb_cdp + +url = "https://www.hyatt.com/" +sb = sb_cdp.Chrome(url, locale="en", guest=True) +sb.sleep(4.2) +sb.click_if_visible('button[aria-label="Close"]') +sb.click_if_visible("#onetrust-reject-all-handler") +sb.sleep(1) +location = "Anaheim, CA, USA" +sb.type('input[id="search-term"]', location) +sb.sleep(1) +sb.click('li[data-js="suggestion"]') +sb.sleep(1) +sb.click("button.be-button-shop") +sb.sleep(6) +card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' +hotels = sb.select_all(card_info) +print("Hyatt Hotels in %s:" % location) +print("(" + sb.get_text("ul.b-color_text-white") + ")") +if len(hotels) == 0: + print("No availability over the selected dates!") +for hotel in hotels: + info = hotel.text.strip() + if "Avg/Night" in info and not info.startswith("Rates from"): + name = info.split(" (")[0].split(" + ")[0].split(" Award Cat")[0] + name = name.split(" Rates from :")[0] + price = "?" + if "Rates from : " in info: + price = info.split("Rates from : ")[1].split(" Avg/Night")[0] + print("* %s => %s" % (name, price)) +sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_kohls.py b/examples/cdp_mode/raw_cdp_kohls.py new file mode 100644 index 00000000000..3ffbe78d88b --- /dev/null +++ b/examples/cdp_mode/raw_cdp_kohls.py @@ -0,0 +1,25 @@ +from seleniumbase import sb_cdp + +url = "https://www.kohls.com/" +sb = sb_cdp.Chrome(url, locale="en", guest=True) +sb.sleep(2.8) +search = "Mickey Mouse 100 friends teal pillow" +required_text = "Mickey" +sb.press_keys('input[name="search"]', search + "\n") +sb.sleep(5) +for item in sb.find_elements("div.products-container-right"): + if "Sponsored" in item.text: + item.remove_from_dom() +sb.remove_elements("#tce-sticky-wrapper") +sb.remove_elements("li.sponsored-product") +sb.remove_elements("#tce-dec-ces-3-banner") +print('*** Kohls Search for "%s":' % search) +for item in sb.find_elements("ul.products a img"): + if item: + item.flash(color="44CC88") + title = item.get_attribute("title") + if title and required_text in title: + print("* " + title) + sb.sleep(0.1) +sb.sleep(1) +sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_nordstrom.py b/examples/cdp_mode/raw_cdp_nordstrom.py new file mode 100644 index 00000000000..4b9257d8e1f --- /dev/null +++ b/examples/cdp_mode/raw_cdp_nordstrom.py @@ -0,0 +1,26 @@ +from seleniumbase import sb_cdp + +url = "https://www.nordstrom.com/" +sb = sb_cdp.Chrome(url, locale="en", guest=True) +sb.sleep(2.2) +sb.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)) +sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_walmart.py b/examples/cdp_mode/raw_cdp_walmart.py new file mode 100644 index 00000000000..5bbf9a8ea95 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_walmart.py @@ -0,0 +1,41 @@ +from seleniumbase import sb_cdp + +url = "https://www.walmart.com/" +sb = sb_cdp.Chrome(url, locale="en", guest=True) +sb.sleep(3) +sb.click('input[aria-label="Search"]') +sb.sleep(1.4) +search = "Settlers of Catan Board Game" +required_text = "Catan" +sb.press_keys('input[aria-label="Search"]', search + "\n") +sb.sleep(3.8) +if sb.is_element_visible("#px-captcha"): + sb.gui_click_and_hold("#px-captcha", 7.2) + sb.sleep(4.2) + if sb.is_element_visible("#px-captcha"): + sb.gui_click_and_hold("#px-captcha", 4.2) + sb.sleep(3.2) +sb.remove_elements('[data-testid="skyline-ad"]') +sb.remove_elements('[data-testid="sba-container"]') +print('*** Walmart Search for "%s":' % search) +print(' (Results must contain "%s".)' % required_text) +unique_item_text = [] +items = sb.find_elements('div[data-testid="list-view"]') +for item in items: + if required_text in item.text: + description = item.querySelector( + '[data-automation-id="product-title"]' + ) + if description and description.text not in unique_item_text: + unique_item_text.append(description.text) + print("* " + description.text) + price = item.querySelector( + '[data-automation-id="product-price"]' + ) + if price: + price_text = price.text + price_text = price_text.split("current price Now ")[-1] + price_text = price_text.split("current price ")[-1] + price_text = price_text.split(" ")[0] + print(" (" + price_text + ")") +sb.driver.stop() diff --git a/examples/cdp_mode/raw_copilot.py b/examples/cdp_mode/raw_copilot.py index 909b91842eb..0a6978612eb 100644 --- a/examples/cdp_mode/raw_copilot.py +++ b/examples/cdp_mode/raw_copilot.py @@ -23,7 +23,7 @@ sb.sleep(3.5) stop_button = '[data-testid="stop-button"]' thumbs_up = 'button[data-testid*="-thumbs-up-"]' - sb.wait_for_element_absent(stop_button, timeout=40) + sb.wait_for_element_absent(stop_button, timeout=45) sb.wait_for_element(thumbs_up, timeout=20) sb.sleep(0.6) sb.click('button[data-testid*="scroll-to-bottom"]') diff --git a/examples/cdp_mode/raw_easyjet.py b/examples/cdp_mode/raw_easyjet.py index 8d6ad4113bb..768cac9cabc 100644 --- a/examples/cdp_mode/raw_easyjet.py +++ b/examples/cdp_mode/raw_easyjet.py @@ -4,27 +4,27 @@ url = "https://www.easyjet.com/en/" sb.activate_cdp_mode(url) sb.sleep(2) - sb.cdp.click_if_visible("button#ensCloseBanner") + sb.click_if_visible("button#ensCloseBanner") sb.sleep(1.2) - sb.cdp.click('input[name="from"]') + sb.click('input[name="from"]') sb.sleep(1.2) - sb.cdp.type('input[name="from"]', "London Gatwick") + sb.type('input[name="from"]', "London Gatwick") sb.sleep(0.6) - sb.cdp.click_if_visible("button#ensCloseBanner") + sb.click_if_visible("button#ensCloseBanner") sb.sleep(0.6) - sb.cdp.click('span[data-testid="airport-name"]') + sb.click('span[data-testid="airport-name"]') sb.sleep(1.2) - sb.cdp.type('input[name="to"]', "Paris") + sb.type('input[name="to"]', "Paris") sb.sleep(1.2) - sb.cdp.click('span[data-testid="airport-name"]') + sb.click('span[data-testid="airport-name"]') sb.sleep(1.2) - sb.cdp.click('input[name="when"]') + sb.click('input[name="when"]') sb.sleep(1.2) - sb.cdp.click('[data-testid="month"]:last-of-type [aria-disabled="false"]') + sb.click('[data-testid="month"]:last-of-type [aria-disabled="false"]') sb.sleep(1.2) - sb.cdp.click('[data-testid="month"]:last-of-type [aria-disabled="false"]') + sb.click('[data-testid="month"]:last-of-type [aria-disabled="false"]') sb.sleep(1.2) - sb.cdp.click('button[data-testid="submit"]') + sb.click('button[data-testid="submit"]') sb.sleep(4.2) sb.connect() sb.sleep(1.2) diff --git a/examples/cdp_mode/raw_kohls.py b/examples/cdp_mode/raw_kohls.py index e6e1da0a0f4..1b57bf427b8 100644 --- a/examples/cdp_mode/raw_kohls.py +++ b/examples/cdp_mode/raw_kohls.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.kohls.com/" sb.activate_cdp_mode(url) - sb.sleep(2.5) + sb.sleep(2.6) search = "Mickey Mouse 100 friends teal pillow" required_text = "Mickey" sb.cdp.press_keys('input[name="search"]', search + "\n") diff --git a/examples/cdp_mode/raw_nordstrom.py b/examples/cdp_mode/raw_nordstrom.py index 297485ba18e..2ef935cadff 100644 --- a/examples/cdp_mode/raw_nordstrom.py +++ b/examples/cdp_mode/raw_nordstrom.py @@ -9,7 +9,7 @@ search = "cocktail dresses for women teal" sb.cdp.press_keys("input#keyword-search-input", search + "\n") sb.sleep(2.2) - for i in range(16): + for i in range(17): sb.cdp.scroll_down(16) sb.sleep(0.14) print('*** Nordstrom Search for "%s":' % search) diff --git a/examples/cdp_mode/raw_united.py b/examples/cdp_mode/raw_united.py index 5136cc2385f..6a371ac7e50 100644 --- a/examples/cdp_mode/raw_united.py +++ b/examples/cdp_mode/raw_united.py @@ -10,17 +10,17 @@ destination = "Orlando, FL" sb.cdp.gui_click_element(origin_input) sb.sleep(0.5) - sb.cdp.type(origin_input, origin) + sb.type(origin_input, origin) sb.sleep(1.2) - sb.cdp.click('strong:contains("%s")' % origin) + sb.click('strong:contains("%s")' % origin) sb.sleep(1.2) sb.cdp.gui_click_element(destination_input) sb.sleep(0.5) - sb.cdp.type(destination_input, destination) + sb.type(destination_input, destination) sb.sleep(1.2) - sb.cdp.click('strong:contains("%s")' % destination) + sb.click('strong:contains("%s")' % destination) sb.sleep(1.2) - sb.cdp.click('button[aria-label="Find flights"]') + sb.click('button[aria-label="Find flights"]') sb.sleep(6) flights = sb.find_elements('div[class*="CardContainer__block"]') print("**** Flights from %s to %s ****" % (origin, destination)) diff --git a/examples/cdp_mode/raw_xpath.py b/examples/cdp_mode/raw_xpath.py index ab8e6e4ac9a..8a0b92e1e28 100644 --- a/examples/cdp_mode/raw_xpath.py +++ b/examples/cdp_mode/raw_xpath.py @@ -1,4 +1,4 @@ -"""Test that CDP Mode can autodetect and use xpath selectors.""" +"""Test that CDP Mode can autodetect and use XPath selectors.""" from seleniumbase import SB with SB(uc=True, test=True) as sb: diff --git a/examples/test_cdp_ad_blocking.py b/examples/test_cdp_ad_blocking.py index 0dea44296da..c23660efe36 100644 --- a/examples/test_cdp_ad_blocking.py +++ b/examples/test_cdp_ad_blocking.py @@ -9,22 +9,20 @@ def test_cdp_network_blocking(self): message = "Skipping test if reusing session or not Chromium!" print(message) self.skip(message) + self.execute_cdp_cmd("Network.enable", {}) self.execute_cdp_cmd( - 'Network.setBlockedURLs', {"urls": [ + "Network.setBlockedURLs", {"urls": [ "*googlesyndication.com*", - "*doubleclick.net*", - "*adsafeprotected.com*", - "*2mdn.net*", "*googletagmanager.com*", + "*google-analytics.com*", + "*amazon-adsystem.com*", "*adsafeprotected.com*", - "*snigelweb.com*", + "*doubleclick.net*", "*fastclick.net*", - "*amazon-adsystem.com*", - "*google-analytics.com*", + "*snigelweb.com*", + "*2mdn.net*", ]}) - self.execute_cdp_cmd('Network.enable', {}) - self.open('https://www.w3schools.com/jquery/default.asp') - self.ad_block() + self.open("https://www.w3schools.com/jquery/default.asp") source = self.get_page_source() self.assert_true("doubleclick.net" not in source) self.assert_true("google-analytics.com" not in source) diff --git a/examples/test_roblox_mobile.py b/examples/test_roblox_mobile.py index 02ed1c13084..82f1f335b98 100644 --- a/examples/test_roblox_mobile.py +++ b/examples/test_roblox_mobile.py @@ -17,7 +17,7 @@ def test_roblox_mobile_site(self): self.skip('Use "--mobile" to run this test in Mobile Mode!') self.open("https://www.roblox.com/") self.assert_element("#download-the-app-container") - self.assert_text("Roblox for Android", "p.roblox-for-platform") - self.assert_text("Continue in App", "a.primary-link") - self.highlight("p.roblox-for-platform", loops=8) - self.highlight("a.primary-link", loops=8) + self.assert_text("Roblox for Android") + self.assert_text("Continue in App", "a.content-action-emphasis") + self.highlight('span:contains("Roblox for Android")', loops=8) + self.highlight("a.content-action-emphasis", loops=8) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 19bff926767..8777c451d22 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -250,8 +250,10 @@ self.save_screenshot_to_logs(name=None, selector=None, by="css selector") self.save_as_pdf_to_logs(name=None) self.save_data_to_logs(data, file_name=None) self.append_data_to_logs(data, file_name=None) -self.save_page_source(name, folder=None) self.save_page_source_to_logs(name=None) +self.save_page_source(name, folder=None) +# Duplicates: +# self.save_as_html(name, folder=None) self.save_cookies(name="cookies.txt") self.load_cookies(name="cookies.txt", expiry=False) self.delete_all_cookies() diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 15f32c3ea14..f3bebc7e166 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,9 +1,9 @@ # mkdocs dependencies for generating the seleniumbase.io website -# Minimum Python version: 3.9 (for generating docs only) +# Minimum Python version: 3.10 (for generating docs only) regex>=2025.9.18 pymdown-extensions>=10.16.1 -pipdeptree>=2.28.0 +pipdeptree>=2.29.0 python-dateutil>=2.8.2 Markdown==3.9 click==8.3.0 diff --git a/requirements.txt b/requirements.txt index ee5052c4d13..a933d1e0b54 100755 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ filelock~=3.16.1;python_version<"3.9" filelock~=3.19.1;python_version>="3.9" and python_version<"3.10" filelock>=3.20.0;python_version>="3.10" fasteners>=0.20 -mycdp>=1.2.0 +mycdp>=1.2.1 pynose>=1.5.5 platformdirs~=4.3.6;python_version<"3.9" platformdirs~=4.4.0;python_version>="3.9" and python_version<"3.10" @@ -66,7 +66,8 @@ pytest-html==4.0.2 pytest-metadata==3.1.1 pytest-ordering==0.6 pytest-rerunfailures==14.0;python_version<"3.9" -pytest-rerunfailures==16.0.1;python_version>="3.9" +pytest-rerunfailures==16.0.1;python_version>="3.9" and python_version<"3.10" +pytest-rerunfailures==16.1;python_version>="3.10" pytest-xdist==3.6.1;python_version<"3.9" pytest-xdist==3.8.0;python_version>="3.9" parameterized==0.9.0 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index e87bd9287b1..ddb7f76a9ea 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.42.4" +__version__ = "4.42.5" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 0c590874a79..ffbd5df8d59 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -756,12 +756,16 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.set_value = CDPM.set_value cdp.submit = CDPM.submit cdp.evaluate = CDPM.evaluate + cdp.execute_script = CDPM.execute_script cdp.js_dumps = CDPM.js_dumps cdp.maximize = CDPM.maximize cdp.minimize = CDPM.minimize cdp.medimize = CDPM.medimize cdp.set_window_rect = CDPM.set_window_rect cdp.reset_window_size = CDPM.reset_window_size + cdp.activate_messenger = CDPM.activate_messenger + cdp.set_messenger_theme = CDPM.set_messenger_theme + cdp.post_message = CDPM.post_message cdp.set_locale = CDPM.set_locale cdp.set_local_storage_item = CDPM.set_local_storage_item cdp.set_session_storage_item = CDPM.set_session_storage_item @@ -877,6 +881,8 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.scroll_to_bottom = CDPM.scroll_to_bottom cdp.scroll_up = CDPM.scroll_up cdp.scroll_down = CDPM.scroll_down + cdp.save_page_source = CDPM.save_page_source + cdp.save_as_html = CDPM.save_as_html cdp.save_screenshot = CDPM.save_screenshot cdp.print_to_pdf = CDPM.print_to_pdf cdp.save_as_pdf = CDPM.save_as_pdf diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 2b0d208df4a..bd3d94d0d41 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1024,6 +1024,9 @@ def evaluate(self, expression): ).strip() return self.loop.run_until_complete(self.page.evaluate(expression)) + def execute_script(self, expression): + return self.evaluate(expression) + def js_dumps(self, obj_name): """Similar to evaluate(), but for dictionary results.""" if obj_name.startswith("return "): @@ -1407,6 +1410,64 @@ def enter_mfa_code(self, selector, totp_key=None, timeout=None): mfa_code = self.get_mfa_code(totp_key) self.type(selector, mfa_code + "\n", timeout=timeout) + def activate_messenger(self): + js_utils.activate_messenger(self) + self.__add_light_pause() + + def set_messenger_theme( + self, theme="default", location="default", max_messages="default" + ): + """Sets a theme for posting messages. + Themes: ["flat", "future", "block", "air", "ice"] + Locations: ["top_left", "top_center", "top_right", + "bottom_left", "bottom_center", "bottom_right"] + max_messages: The limit of concurrent messages to display.""" + if not theme: + theme = "default" # "flat" + if not location: + location = "default" # "bottom_right" + if not max_messages: + max_messages = "default" # "8" + else: + max_messages = str(max_messages) # Value must be in string format + js_utils.set_messenger_theme( + self, + theme=theme, + location=location, + max_messages=max_messages, + ) + self.__add_light_pause() + + def post_message(self, message, duration=None, pause=True, style="info"): + """Post a message on the screen with Messenger. + Arguments: + message: The message to display. + duration: The time until the message vanishes. (Default: 2.55s) + pause: If True, the program waits until the message completes. + style: "info", "success", or "error".""" + driver = self.driver + if hasattr(driver, "cdp_base"): + driver = driver.cdp_base + if style not in ["info", "success", "error"]: + style = "info" + if not duration: + duration = settings.DEFAULT_MESSAGE_DURATION + if ( + ( + driver.config.headless + or (hasattr(sb_config, "xvfb") and sb_config.xvfb) + ) + and float(duration) > 0.75 + ): + duration = 0.75 + try: + js_utils.post_message(self, message, duration, style=style) + except Exception: + print(" * %s message: %s" % (style.upper(), message)) + if pause: + duration = float(duration) + 0.15 + time.sleep(float(duration)) + def set_locale(self, locale): """(Settings will take effect on the next page load)""" self.loop.run_until_complete(self.page.set_locale(locale)) @@ -1510,7 +1571,10 @@ def __install_pyautogui_if_missing(self): except Exception: if ( shared_utils.is_linux() - and (not sb_config.headed or sb_config.xvfb) + and ( + not sb_config.headed + or (hasattr(sb_config, "xvfb") and sb_config.xvfb) + ) and not driver.config.headless and ( not hasattr(sb_config, "_virtual_display") @@ -2611,6 +2675,40 @@ def scroll_down(self, amount=25): self.loop.run_until_complete(self.page.scroll_down(amount)) self.loop.run_until_complete(self.page.wait()) + def save_page_source(self, name, folder=None): + import codecs + from seleniumbase.core import log_helper + if not name.endswith(".html"): + name = name + ".html" + if folder: + abs_path = os.path.abspath(".") + file_path = os.path.join(abs_path, folder) + if not os.path.exists(file_path): + os.makedirs(file_path) + html_file_path = os.path.join(file_path, name) + else: + html_file_path = name + page_source = self.get_page_source() + last_page = self.get_current_url() + meta_charset = '' + rendered_source = "" + if "://" in last_page: + base_href_html = log_helper.get_base_href_html(last_page) + if ' charset="' not in page_source: + rendered_source = "%s\n%s\n%s" % ( + base_href_html, meta_charset, page_source + ) + else: + rendered_source = "%s\n%s" % (base_href_html, page_source) + else: + rendered_source = page_source + html_file = codecs.open(html_file_path, "w+", "utf-8") + html_file.write(rendered_source) + html_file.close() + + def save_as_html(self, *args, **kwargs): + self.save_page_source(*args, **kwargs) + def save_screenshot(self, name, folder=None, selector=None): filename = name if folder: diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 5803b437c6e..a6f61b48d0e 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -9205,6 +9205,10 @@ def switch_to_newest_tab(self): """Same as self.switch_to_newest_window()""" self.switch_to_newest_window() + def save_as_html(self, name, folder=None): + """Same as self.save_page_source()""" + self.save_page_source(name, folder=folder) + def input( self, selector, text, by="css selector", timeout=None, retry=False ): @@ -9654,9 +9658,11 @@ def add_meta_tag(self, http_equiv=None, content=None): def activate_messenger(self): self.__check_scope() - self._check_browser() + if not self.__is_cdp_swap_needed(): + self._check_browser() js_utils.activate_messenger(self.driver) - self.wait_for_ready_state_complete() + if not self.__is_cdp_swap_needed(): + self.wait_for_ready_state_complete() def set_messenger_theme( self, theme="default", location="default", max_messages="default" diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index 18400189c4f..5178b21ff0a 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -29,6 +29,8 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT): If the timeout is exceeded, the test will still continue because readyState == "interactive" may be good enough. (Previously, tests would fail immediately if exceeding the timeout.)""" + if hasattr(driver, "_swap_driver"): + return if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS: return start_ms = time.time() * 1000.0 @@ -54,8 +56,12 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT): def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT): - driver.set_script_timeout(timeout) - return driver.execute_async_script(script) + if hasattr(driver, "set_script_timeout"): + driver.set_script_timeout(timeout) + if hasattr(driver, "execute_async_script"): + return driver.execute_async_script(script) + else: + return None def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): @@ -937,7 +943,10 @@ def post_message(driver, message, msg_dur=None, style="info"): execute_script(driver, messenger_script) except TypeError as e: if ( - shared_utils.is_cdp_swap_needed(driver) + ( + shared_utils.is_cdp_swap_needed(driver) + or hasattr(driver, "_swap_driver") + ) and "cannot unpack non-iterable" in str(e) ): pass diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 5400250970f..fd51e22ec68 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1528,10 +1528,10 @@ def save_page_source(driver, name, folder=None): page_source = driver.cdp.get_page_source() else: page_source = driver.page_source - html_file = codecs.open(html_file_path, "w+", "utf-8") rendered_source = log_helper.get_html_source_with_base_href( driver, page_source ) + html_file = codecs.open(html_file_path, "w+", "utf-8") html_file.write(rendered_source) html_file.close() diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index 8d733fc1b5f..9ca7f5594d6 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -335,6 +335,8 @@ async def start( xvfb = True else: xvfb = False + if not hasattr(sb_config, "xvfb"): + sb_config.xvfb = xvfb if incognito is None: if "--incognito" in sys_argv: incognito = True diff --git a/seleniumbase/undetected/cdp_driver/config.py b/seleniumbase/undetected/cdp_driver/config.py index 846c35fec77..a36248a4903 100644 --- a/seleniumbase/undetected/cdp_driver/config.py +++ b/seleniumbase/undetected/cdp_driver/config.py @@ -202,6 +202,7 @@ def __call__(self): "InsecureDownloadWarnings,DownloadBubble,DownloadBubbleV2," "OptimizationTargetPrediction,OptimizationGuideModelDownloading," "SidePanelPinning,UserAgentClientHint,PrivacySandboxSettings4," + "OptimizationHintsFetching,InterestFeedContentSuggestions," "DisableLoadExtensionCommandLineSwitch" ] if self.proxy: diff --git a/setup.py b/setup.py index e6e5b3d610c..0003bf56a87 100755 --- a/setup.py +++ b/setup.py @@ -161,7 +161,7 @@ 'filelock~=3.19.1;python_version>="3.9" and python_version<"3.10"', 'filelock>=3.20.0;python_version>="3.10"', 'fasteners>=0.20', - "mycdp>=1.2.0", + "mycdp>=1.2.1", "pynose>=1.5.5", 'platformdirs~=4.3.6;python_version<"3.9"', 'platformdirs~=4.4.0;python_version>="3.9" and python_version<"3.10"', @@ -213,7 +213,8 @@ 'pytest-metadata==3.1.1', "pytest-ordering==0.6", 'pytest-rerunfailures==14.0;python_version<"3.9"', - 'pytest-rerunfailures==16.0.1;python_version>="3.9"', + 'pytest-rerunfailures==16.0.1;python_version>="3.9" and python_version<"3.10"', # noqa + 'pytest-rerunfailures==16.1;python_version>="3.10"', 'pytest-xdist==3.6.1;python_version<"3.9"', 'pytest-xdist==3.8.0;python_version>="3.9"', 'parameterized==0.9.0',