diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 88e3d6f29a1..37ff56aad52 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -405,6 +405,7 @@ sb.cdp.flash(selector, duration=1, color="44CC88", pause=0) sb.cdp.highlight(selector) sb.cdp.focus(selector) sb.cdp.highlight_overlay(selector) +sb.cdp.get_parent(element) sb.cdp.remove_element(selector) sb.cdp.remove_from_dom(selector) sb.cdp.remove_elements(selector) @@ -522,6 +523,7 @@ element.get_position() element.get_html() element.get_js_attributes() element.get_attribute(attribute) +element.get_parent() ``` -------- diff --git a/examples/cdp_mode/raw_cf.py b/examples/cdp_mode/raw_cf.py index 744bb55afe0..0d64a2d20e5 100644 --- a/examples/cdp_mode/raw_cf.py +++ b/examples/cdp_mode/raw_cf.py @@ -4,7 +4,7 @@ with SB(uc=True, test=True, locale_code="en") as sb: url = "https://www.cloudflare.com/login" sb.activate_cdp_mode(url) - sb.sleep(2) + sb.sleep(3) sb.uc_gui_handle_captcha() # PyAutoGUI press Tab and Spacebar sb.sleep(2) diff --git a/examples/cdp_mode/raw_chatgpt.py b/examples/cdp_mode/raw_chatgpt.py index a3ba9577fc6..fd67456c972 100644 --- a/examples/cdp_mode/raw_chatgpt.py +++ b/examples/cdp_mode/raw_chatgpt.py @@ -4,13 +4,18 @@ with SB(uc=True, test=True, ad_block=True) as sb: url = "https://chatgpt.com/" sb.activate_cdp_mode(url) + sb.sleep(1) + sb.click_if_visible('button[aria-label="Close dialog"]') query = "Compare Playwright to SeleniumBase in under 178 words" sb.press_keys("#prompt-textarea", query) sb.click('button[data-testid="send-button"]') print('*** Input for ChatGPT: ***\n"%s"' % query) + sb.sleep(3) with suppress(Exception): - # The "Send" button reappears when ChatGPT is done typing a response - sb.wait_for_element('button[data-testid="send-button"]', timeout=22) + # The "Stop" button disappears when ChatGPT is done typing a response + sb.wait_for_element_not_visible( + 'button[data-testid="stop-button"]', timeout=20 + ) chat = sb.find_element('[data-message-author-role="assistant"] .markdown') soup = sb.get_beautiful_soup(chat.get_html()).get_text("\n").strip() print("*** Response from ChatGPT: ***\n%s" % soup.replace("\n:", ":")) diff --git a/examples/cdp_mode/raw_xhr_async.py b/examples/cdp_mode/raw_xhr_async.py index bf0dd3e6fcd..6f2f3c6e6ec 100644 --- a/examples/cdp_mode/raw_xhr_async.py +++ b/examples/cdp_mode/raw_xhr_async.py @@ -65,8 +65,8 @@ async def crawl(): # Change url to something that makes ajax requests tab = await driver.get("https://learn.microsoft.com/en-us/") time.sleep(2) - for i in range(75): - await tab.scroll_down(3) + for i in range(20): + await tab.scroll_down(4) time.sleep(0.02) xhr_responses = await receiveXHR(tab, xhr_requests) @@ -87,6 +87,5 @@ async def crawl(): if __name__ == "__main__": print("<============= START: XHR Example =============>") - loop = asyncio.new_event_loop() - loop.run_until_complete(crawl()) + asyncio.run(crawl()) print("<============== END: XHR Example ==============>") diff --git a/examples/cdp_mode/raw_xhr_sb.py b/examples/cdp_mode/raw_xhr_sb.py index 51b55dde60f..2475fe7fc8f 100644 --- a/examples/cdp_mode/raw_xhr_sb.py +++ b/examples/cdp_mode/raw_xhr_sb.py @@ -64,8 +64,8 @@ async def receiveXHR(page, requests): # Change url to something that makes ajax requests sb.cdp.open("https://learn.microsoft.com/en-us/") time.sleep(2) - for i in range(15): - sb.cdp.scroll_down(15) + for i in range(10): + sb.cdp.scroll_down(8) loop = sb.cdp.get_event_loop() xhr_responses = loop.run_until_complete(receiveXHR(tab, xhr_requests)) diff --git a/examples/raw_games.py b/examples/raw_games.py index a9d5cb9b45a..4f3652f7596 100644 --- a/examples/raw_games.py +++ b/examples/raw_games.py @@ -1,13 +1,14 @@ """SB Manager using UC Mode for evading bot-detection.""" from seleniumbase import SB -with SB(uc=True, test=True) as sb: +with SB(uc=True, test=True, disable_csp=True) as sb: url = "https://steamdb.info/" sb.uc_open_with_reconnect(url, 3) sb.uc_click("a.header-login span", 3) sb.uc_gui_click_captcha() - sb.assert_text("Sign in", "button#js-sign-in") + sb.assert_text("Sign in", "button#js-sign-in", timeout=4) sb.uc_click("button#js-sign-in", 2) sb.highlight("div.page_content form") sb.highlight('button:contains("Sign in")', scroll=False) - sb.sleep(1) + sb.set_messenger_theme(location="top_center") + sb.post_message("SeleniumBase wasn't detected", duration=4) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index ccce5ae7562..42a1902b157 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -82,6 +82,7 @@ self.set_attributes(selector, attribute, value, by="css selector") self.remove_attribute(selector, attribute, by="css selector", timeout=None) self.remove_attributes(selector, attribute, by="css selector") self.internalize_links() +self.get_parent(element, by="css selector", timeout=None) self.get_property(selector, property, by="css selector", timeout=None) self.get_text_content(selector="html", by="css selector", timeout=None) self.get_property_value(selector, property, by="css selector", timeout=None) @@ -743,6 +744,8 @@ driver.highlight_if_visible(selector) driver.sleep(seconds) driver.locator(selector) driver.get_attribute(selector, attribute) +driver.get_parent(element) +driver.get_current_url() driver.get_page_source() driver.get_title() driver.switch_to_frame(frame="iframe") diff --git a/requirements.txt b/requirements.txt index 00fda432eeb..ff2bee17e98 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -pip>=25.0 +pip>=25.0.1 packaging>=24.2 setuptools~=70.2;python_version<"3.10" setuptools>=75.8.0;python_version>="3.10" @@ -70,7 +70,7 @@ rich==13.9.4 # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) coverage>=7.6.1;python_version<"3.9" -coverage>=7.6.10;python_version>="3.9" +coverage>=7.6.12;python_version>="3.9" pytest-cov>=5.0.0;python_version<"3.9" pytest-cov>=6.0.0;python_version>="3.9" flake8==5.0.4;python_version<"3.9" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index e5ff0e8c6e1..efed7ed2220 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.34.12" +__version__ = "4.34.13" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 10610fed832..cf5b82cedba 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -220,6 +220,7 @@ def extend_driver(driver): driver.highlight_if_visible = DM.highlight_if_visible driver.sleep = time.sleep driver.get_attribute = DM.get_attribute + driver.get_parent = DM.get_parent driver.get_current_url = DM.get_current_url driver.get_page_source = DM.get_page_source driver.get_title = DM.get_title @@ -647,6 +648,7 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.click_if_visible = CDPM.click_if_visible cdp.click_visible_elements = CDPM.click_visible_elements cdp.mouse_click = CDPM.mouse_click + cdp.get_parent = CDPM.get_parent cdp.remove_element = CDPM.remove_element cdp.remove_from_dom = CDPM.remove_from_dom cdp.remove_elements = CDPM.remove_elements @@ -5394,6 +5396,19 @@ def get_local_driver( ) driver._is_hidden = (headless or headless2) driver._is_using_uc = True + with suppress(Exception): + if int(uc_driver_version) >= 133: + for window_handle in driver.window_handles: + driver.switch_to.window(window_handle) + if driver.current_url.startswith( + "chrome-extension://" + ): + driver.close() + time.sleep(0.003) + driver.switch_to.window(driver.window_handles[0]) + time.sleep(0.003) + 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 98755cc7c1b..2a87ec0a389 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -91,6 +91,8 @@ def __add_sync_methods(self, element): element.get_attribute = ( lambda attribute: self.__get_attribute(element, attribute) ) + # element.get_parent() should come last + element.get_parent = lambda: self.__get_parent(element) return element def get(self, url): @@ -549,6 +551,9 @@ def __get_attribute(self, element, attribute): pass return None + def __get_parent(self, element): + return self.__add_sync_methods(element.parent) + def __get_x_scroll_offset(self): x_scroll_offset = self.loop.run_until_complete( self.page.evaluate("window.pageXOffset") @@ -769,6 +774,11 @@ def focus(self, selector): def highlight_overlay(self, selector): self.find_element(selector).highlight_overlay() + def get_parent(self, element): + if isinstance(element, str): + element = self.select(element) + return self.__add_sync_methods(element.parent) + def remove_element(self, selector): self.select(selector).remove_from_dom() diff --git a/seleniumbase/core/sb_driver.py b/seleniumbase/core/sb_driver.py index 35147607c86..0ee4744c3b3 100644 --- a/seleniumbase/core/sb_driver.py +++ b/seleniumbase/core/sb_driver.py @@ -51,6 +51,13 @@ def get_attribute(self, selector, attribute, by="css selector"): element = self.locator(selector, by=by) return element.get_attribute(attribute) + def get_parent(self, element): + if self.__is_cdp_swap_needed(): + return self.driver.cdp.get_parent(element) + if isinstance(element, str): + element = self.locator(element) + return element.find_element(by="xpath", value="..") + def get_current_url(self): if self.__is_cdp_swap_needed(): current_url = self.driver.cdp.get_current_url() diff --git a/seleniumbase/extensions/ad_block.zip b/seleniumbase/extensions/ad_block.zip index fd27f299aa3..ec27557cfb1 100644 Binary files a/seleniumbase/extensions/ad_block.zip and b/seleniumbase/extensions/ad_block.zip differ diff --git a/seleniumbase/extensions/disable_csp.zip b/seleniumbase/extensions/disable_csp.zip index 718326c9f90..9a484e5790d 100644 Binary files a/seleniumbase/extensions/disable_csp.zip and b/seleniumbase/extensions/disable_csp.zip differ diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index be4207bfdfd..dca2b26e781 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -2072,6 +2072,19 @@ def internalize_links(self): return self.set_attributes('[target="_blank"]', "target", "_self") + def get_parent(self, element, by="css selector", timeout=None): + """Returns the parent element. + If element is a string, then finds element first via selector.""" + if self.__is_cdp_swap_needed(): + return self.cdp.get_parent(element) + if isinstance(element, str): + if not timeout: + timeout = settings.LARGE_TIMEOUT + element = self.wait_for_element_present( + element, by=by, timeout=timeout + ) + return element.find_element(by="xpath", value="..") + def get_property( self, selector, property, by="css selector", timeout=None ): diff --git a/setup.py b/setup.py index a865f4746fb..c69e13ae49a 100755 --- a/setup.py +++ b/setup.py @@ -147,7 +147,7 @@ ], python_requires=">=3.8", install_requires=[ - 'pip>=25.0', + 'pip>=25.0.1', 'packaging>=24.2', 'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues 'setuptools>=75.8.0;python_version>="3.10"', @@ -228,7 +228,7 @@ # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ 'coverage>=7.6.1;python_version<"3.9"', - 'coverage>=7.6.10;python_version>="3.9"', + 'coverage>=7.6.12;python_version>="3.9"', 'pytest-cov>=5.0.0;python_version<"3.9"', 'pytest-cov>=6.0.0;python_version>="3.9"', ], @@ -259,7 +259,7 @@ "pdfminer": [ 'pdfminer.six==20240706', 'cryptography==39.0.2;python_version<"3.9"', - 'cryptography==44.0.0;python_version>="3.9"', + 'cryptography==44.0.1;python_version>="3.9"', 'cffi==1.17.1', "pycparser==2.22", ],