diff --git a/examples/presenter/multi_uc.py b/examples/presenter/multi_uc.py index 37f621dacf4..b1e64a82bf3 100644 --- a/examples/presenter/multi_uc.py +++ b/examples/presenter/multi_uc.py @@ -7,16 +7,16 @@ @pytest.mark.parametrize("", [[]] * 3) def test_multi_threaded(sb): - sb.driver.uc_open_with_tab("https://nowsecure.nl/#relax") + sb.driver.uc_open_with_reconnect("https://top.gg/", 5) sb.set_window_rect(randint(0, 755), randint(38, 403), 700, 500) try: - sb.assert_text("OH YEAH, you passed!", "h1", timeout=4) + sb.assert_text("Discord Bots", "h1", timeout=2) sb.post_message("Selenium wasn't detected!", duration=4) sb._print("\n Success! Website did not detect Selenium! ") except Exception: - sb.driver.uc_open_with_tab("https://nowsecure.nl/#relax") + sb.driver.uc_open_with_reconnect("https://top.gg/", 5) try: - sb.assert_text("OH YEAH, you passed!", "h1", timeout=4) + sb.assert_text("Discord Bots", "h1", timeout=2) sb.post_message("Selenium wasn't detected!", duration=4) sb._print("\n Success! Website did not detect Selenium! ") except Exception: diff --git a/examples/presenter/uc_presentation.py b/examples/presenter/uc_presentation.py index d8cb4b082f5..cadf761f58c 100644 --- a/examples/presenter/uc_presentation.py +++ b/examples/presenter/uc_presentation.py @@ -30,17 +30,16 @@ def test_presentation(self): self.get_new_driver(undetectable=True) try: self.driver.uc_open_with_reconnect( - "https://nowsecure.nl/#relax", reconnect_time=3 + "https://top.gg/", reconnect_time=4 ) try: - self.assert_text("OH YEAH, you passed!", "h1", timeout=4) + self.assert_text("Discord Bots", "h1", timeout=3) self.post_message("Selenium wasn't detected!", duration=4) except Exception: - self.clear_all_cookies() self.driver.uc_open_with_reconnect( - "https://nowsecure.nl/#relax", reconnect_time=3 + "https://top.gg/", reconnect_time=5 ) - self.assert_text("OH YEAH, you passed!", "h1", timeout=4) + self.assert_text("Discord Bots", "h1", timeout=2) self.post_message("Selenium wasn't detected!", duration=4) finally: self.quit_extra_driver() diff --git a/examples/raw_cdp_logging.py b/examples/raw_cdp_logging.py new file mode 100644 index 00000000000..b9e3ed8286e --- /dev/null +++ b/examples/raw_cdp_logging.py @@ -0,0 +1,10 @@ +from rich.pretty import pprint +from seleniumbase import Driver + +driver = Driver(uc=True, log_cdp=True) +try: + driver.get("https://seleniumbase.io/apps/invisible_recaptcha") + driver.sleep(3) + pprint(driver.get_log("performance")) +finally: + driver.quit() diff --git a/examples/raw_driver_context.py b/examples/raw_driver_context.py index ba4ec661793..116bca3513b 100644 --- a/examples/raw_driver_context.py +++ b/examples/raw_driver_context.py @@ -1,8 +1,8 @@ -"""Can run with "python". (pytest not needed).""" +"""DriverContext() example. (Runs with "python").""" from seleniumbase import DriverContext with DriverContext() as driver: - driver.open("seleniumbase.github.io/") + driver.open("seleniumbase.io/") driver.highlight('img[alt="SeleniumBase"]', loops=6) with DriverContext(browser="chrome", incognito=True) as driver: @@ -13,7 +13,7 @@ driver.highlight("#output", loops=6) with DriverContext() as driver: - driver.open("seleniumbase.github.io/demo_page") + driver.open("seleniumbase.io/demo_page") driver.highlight("h2") driver.type("#myTextInput", "Automation") driver.click("#checkBox1") diff --git a/examples/raw_browser_launcher.py b/examples/raw_driver_manager.py similarity index 78% rename from examples/raw_browser_launcher.py rename to examples/raw_driver_manager.py index 9069b9912f4..d7db9f9b793 100644 --- a/examples/raw_browser_launcher.py +++ b/examples/raw_driver_manager.py @@ -1,6 +1,16 @@ -"""Driver() test. Runs with "python". (pytest not needed).""" +"""Driver() manager example. (Runs with "python").""" from seleniumbase import Driver +driver = Driver() +try: + driver.open("seleniumbase.io/demo_page") + driver.highlight("h2") + driver.type("#myTextInput", "Automation") + driver.click("#checkBox1") + driver.highlight("img", loops=6) +finally: + driver.quit() + driver = Driver(browser="chrome", headless=False) try: driver.open("seleniumbase.io/apps/calculator") @@ -10,13 +20,3 @@ driver.highlight("#output", loops=6) finally: driver.quit() - -driver = Driver() -try: - driver.open("seleniumbase.github.io/demo_page") - driver.highlight("h2") - driver.type("#myTextInput", "Automation") - driver.click("#checkBox1") - driver.highlight("img", loops=6) -finally: - driver.quit() diff --git a/examples/raw_sb.py b/examples/raw_sb.py index f0428fa2da9..2f34b33397e 100644 --- a/examples/raw_sb.py +++ b/examples/raw_sb.py @@ -1,4 +1,4 @@ -"""Context Manager Test. Runs with "python". (pytest not needed).""" +"""SB() context manager example. (Runs with "python").""" from seleniumbase import SB with SB() as sb: # By default, browser="chrome" if not set. diff --git a/examples/raw_uc_mode.py b/examples/raw_uc_mode.py index 11ccd34b4a2..b4804602635 100644 --- a/examples/raw_uc_mode.py +++ b/examples/raw_uc_mode.py @@ -2,18 +2,9 @@ from seleniumbase import SB with SB(uc=True, test=True) as sb: - sb.driver.uc_open_with_tab("https://nowsecure.nl/#relax") - sb.sleep(1.2) - if not sb.is_text_visible("OH YEAH, you passed!", "h1"): + sb.driver.uc_open_with_reconnect("https://top.gg/", 4) + if not sb.is_text_visible("Discord Bots", "h1"): sb.get_new_driver(undetectable=True) - sb.driver.uc_open_with_reconnect( - "https://nowsecure.nl/#relax", reconnect_time=3 - ) - sb.sleep(1.2) - if not sb.is_text_visible("OH YEAH, you passed!", "h1"): - if sb.is_element_visible('iframe[src*="challenge"]'): - with sb.frame_switch('iframe[src*="challenge"]'): - sb.click("span.mark") - sb.sleep(2) - sb.activate_demo_mode() - sb.assert_text("OH YEAH, you passed!", "h1", timeout=3) + sb.driver.uc_open_with_reconnect("https://top.gg/", 5) + sb.activate_demo_mode() # Highlight + show assertions + sb.assert_text("Discord Bots", "h1", timeout=3) diff --git a/examples/uc_cdp_events.py b/examples/uc_cdp_events.py index 745cf7a07e3..17c1c7615d9 100644 --- a/examples/uc_cdp_events.py +++ b/examples/uc_cdp_events.py @@ -1,4 +1,4 @@ -from pprint import pformat +from rich.pretty import pprint from seleniumbase import BaseCase BaseCase.main(__name__, __file__, "--uc", "--uc-cdp", "-s") @@ -9,38 +9,18 @@ def add_cdp_listener(self): # self.driver.add_cdp_listener("*", lambda data: print(pformat(data))) self.driver.add_cdp_listener( "Network.requestWillBeSentExtraInfo", - lambda data: print(pformat(data)) + lambda data: pprint(data) ) def verify_success(self): self.assert_text("OH YEAH, you passed!", "h1", timeout=6.25) self.sleep(1) - def fail_me(self): - self.fail('Selenium was detected! Try using: "pytest --uc"') - def test_display_cdp_events(self): if not (self.undetectable and self.uc_cdp_events): self.get_new_driver(undetectable=True, uc_cdp_events=True) - self.driver.get("https://nowsecure.nl/#relax") - try: - self.verify_success() - except Exception: - self.clear_all_cookies() - self.get_new_driver(undetectable=True, uc_cdp_events=True) - self.driver.get("https://nowsecure.nl/#relax") - try: - self.verify_success() - except Exception: - if self.is_element_visible('iframe[src*="challenge"]'): - with self.frame_switch('iframe[src*="challenge"]'): - self.click("span.mark") - else: - self.fail_me() - try: - self.verify_success() - except Exception: - self.fail_me() + self.driver.uc_open_with_tab("https://nowsecure.nl/#relax") + self.verify_success() self.add_cdp_listener() self.refresh() self.sleep(1) diff --git a/examples/verify_undetected.py b/examples/verify_undetected.py index ee0d3052777..fe01fc880f0 100644 --- a/examples/verify_undetected.py +++ b/examples/verify_undetected.py @@ -10,20 +10,14 @@ def test_browser_is_undetected(self): if not self.undetectable: self.get_new_driver(undetectable=True) self.driver.uc_open_with_reconnect( - "https://nowsecure.nl/#relax", reconnect_time=3 + "https://top.gg/", reconnect_time=4 ) - self.sleep(1.2) - if not self.is_text_visible("OH YEAH, you passed!", "h1"): + if not self.is_text_visible("Discord Bots", "h1"): self.get_new_driver(undetectable=True) self.driver.uc_open_with_reconnect( - "https://nowsecure.nl/#relax", reconnect_time=3 + "https://top.gg/", reconnect_time=5 ) - self.sleep(1.2) - if not self.is_text_visible("OH YEAH, you passed!", "h1"): - if self.is_element_visible('iframe[src*="challenge"]'): - with self.frame_switch('iframe[src*="challenge"]'): - self.click("span.mark") - self.sleep(2) - self.assert_text("OH YEAH, you passed!", "h1", timeout=3) + self.assert_text("Discord Bots", "h1", timeout=3) + self.set_messenger_theme(theme="air", location="top_center") self.post_message("Selenium wasn't detected!", duration=2.8) self._print("\n Success! Website did not detect Selenium! ") diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md index d0393564f24..f9a8c9c2b5e 100644 --- a/help_docs/syntax_formats.md +++ b/help_docs/syntax_formats.md @@ -832,17 +832,16 @@ This format provides a pure Python way of using SeleniumBase without a test runn ```python from seleniumbase import SB -with SB() as sb: # By default, browser="chrome" if not set. - sb.open("https://seleniumbase.github.io/realworld/login") +with SB() as sb: + sb.open("seleniumbase.io/simple/login") sb.type("#username", "demo_user") sb.type("#password", "secret_pass") - sb.enter_mfa_code("#totpcode", "GAXG2MTEOR3DMMDG") # 6-digit - sb.assert_text("Welcome!", "h1") - sb.highlight("img#image1") # A fancier assert_element() call - sb.click('a:contains("This Page")') # Use :contains() on any tag - sb.click_link("Sign out") # Link must be "a" tag. Not "button". - sb.assert_element('a:contains("Sign in")') - sb.assert_exact_text("You have been signed out!", "#top_message") + sb.click('a:contains("Sign in")') + sb.assert_exact_text("Welcome!", "h1") + sb.assert_element("img#image1") + sb.highlight("#image1") + sb.click_link("Sign out") + sb.assert_text("signed out", "#top_message") ``` (See examples/raw_sb.py for the test.) @@ -881,11 +880,11 @@ with SB(test=True, rtf=True, demo=True) as sb: This pure Python format gives you a raw webdriver instance in a with block. The SeleniumBase Driver Manager will automatically make sure that your driver is compatible with your browser version. It gives you full access to customize driver options via method args or via the command-line. The driver will automatically call quit() after the code leaves the with block. Here are some examples: ```python -"""Can run with "python". (pytest not needed).""" +"""DriverContext() example. (Runs with "python").""" from seleniumbase import DriverContext with DriverContext() as driver: - driver.open("seleniumbase.github.io/") + driver.open("seleniumbase.io/") driver.highlight('img[alt="SeleniumBase"]', loops=6) with DriverContext(browser="chrome", incognito=True) as driver: @@ -896,7 +895,7 @@ with DriverContext(browser="chrome", incognito=True) as driver: driver.highlight("#output", loops=6) with DriverContext() as driver: - driver.open("seleniumbase.github.io/demo_page") + driver.open("seleniumbase.io/demo_page") driver.highlight("h2") driver.type("#myTextInput", "Automation") driver.click("#checkBox1") @@ -911,9 +910,19 @@ with DriverContext() as driver: Another way of running Selenium tests with pure ``python`` (as opposed to using ``pytest`` or ``pynose``) is by using this format, which bypasses [BaseCase](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py) methods while still giving you a flexible driver with a manager. SeleniumBase includes helper files such as [page_actions.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/page_actions.py), which may help you get around some of the limitations of bypassing ``BaseCase``. Here's an example: ```python -"""Driver() test. Runs with "python". (pytest not needed).""" +"""Driver() example. (Runs with "python").""" from seleniumbase import Driver +driver = Driver() +try: + driver.open("seleniumbase.io/demo_page") + driver.highlight("h2") + driver.type("#myTextInput", "Automation") + driver.click("#checkBox1") + driver.highlight("img", loops=6) +finally: + driver.quit() + driver = Driver(browser="chrome", headless=False) try: driver.open("seleniumbase.io/apps/calculator") @@ -923,19 +932,9 @@ try: driver.highlight("#output", loops=6) finally: driver.quit() - -driver = Driver() -try: - driver.open("seleniumbase.github.io/demo_page") - driver.highlight("h2") - driver.type("#myTextInput", "Automation") - driver.click("#checkBox1") - driver.highlight("img", loops=6) -finally: - driver.quit() ``` -(From examples/raw_browser_launcher.py) +(From examples/raw_driver_manager.py) Here's how the [selenium-wire](https://github.com/wkeeling/selenium-wire) integration may look when using the ``Driver()`` format: @@ -951,6 +950,22 @@ finally: driver.quit() ``` +Here's another `selenium-wire` example with the `Driver()` format: + +```python +from seleniumbase import Driver + +def intercept_response(request, response): + print(request.headers) + +driver = Driver(wire=True) +try: + driver.response_interceptor = intercept_response + driver.get("https://wikipedia.org") +finally: + driver.quit() +``` + Here's an example of basic login with the ``Driver()`` format: ```python diff --git a/requirements.txt b/requirements.txt index b08878a455a..ba67d6ac553 100755 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ charset-normalizer==3.3.2 urllib3>=1.26.18,<2;python_version<"3.10" urllib3>=1.26.18,<2.3.0;python_version>="3.10" requests==2.31.0 -pynose==1.4.8 +pynose==1.5.0 sniffio==1.3.0 h11==0.14.0 outcome==1.3.0.post0 @@ -29,7 +29,7 @@ trio==0.24.0;python_version>="3.8" trio-websocket==0.11.1 wsproto==1.2.0 selenium==4.11.2;python_version<"3.8" -selenium==4.17.2;python_version>="3.8" +selenium==4.18.1;python_version>="3.8" cssselect==1.2.0 sortedcontainers==2.4.0 fasteners==0.19 @@ -69,7 +69,7 @@ rich==13.7.0 coverage==6.2;python_version<"3.7" coverage==7.2.7;python_version>="3.7" and python_version<"3.8" -coverage==7.4.1;python_version>="3.8" +coverage==7.4.2;python_version>="3.8" pytest-cov==4.0.0;python_version<"3.7" pytest-cov==4.1.0;python_version>="3.7" flake8==5.0.4;python_version<"3.9" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index cc6efa061ea..8df0a114552 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.23.7" +__version__ = "4.24.0" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 29f96ffa3bd..2f50ea576e5 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -3833,8 +3833,10 @@ def get_local_driver( ) return extend_driver(driver) except Exception: + if is_using_uc(undetectable, browser_name): + raise + # Try again if Chrome didn't launch try: - # Try again if Chrome didn't launch service = ChromeService(service_args=["--disable-build-check"]) driver = webdriver.Chrome( service=service, options=chrome_options @@ -3842,8 +3844,18 @@ def get_local_driver( return extend_driver(driver) except Exception: pass - if headless: + if user_data_dir: + print("\nUnable to set user_data_dir while starting Chrome!\n") + raise + elif mobile_emulator: + print("\nFailed to start Chrome's mobile device emulator!\n") + raise + elif extension_zip or extension_dir: + print("\nUnable to load extension while starting Chrome!\n") + raise + elif headless or headless2 or IS_LINUX or proxy_string or use_wire: raise + # Try running without any options (bare bones Chrome launch) if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER): try: make_driver_executable_if_not(LOCAL_CHROMEDRIVER) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index c9204a82ad8..da5c0fd192f 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -2162,9 +2162,9 @@ def find_visible_elements(self, selector, by="css selector", limit=0): selector, by = self.__recalculate_selector(selector, by) self.wait_for_ready_state_complete() time.sleep(0.05) - v_elems = page_actions.find_visible_elements(self.driver, selector, by) - if limit and limit > 0 and len(v_elems) > limit: - v_elems = v_elems[:limit] + v_elems = page_actions.find_visible_elements( + self.driver, selector, by, limit + ) return v_elems def click_visible_elements( diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index 350ffb64ae0..3cc130b059d 100644 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -367,7 +367,7 @@ class Mobile: class UC: - RECONNECT_TIME = 2.32 # Seconds + RECONNECT_TIME = 2.35 # Seconds class ValidBrowsers: diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 877874fae07..33eb3f1a84b 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1251,26 +1251,40 @@ def wait_for_attribute_not_present( timeout_exception(Exception, message) -def find_visible_elements(driver, selector, by="css selector"): +def find_visible_elements(driver, selector, by="css selector", limit=0): """ Finds all WebElements that match a selector and are visible. - Similar to webdriver.find_elements. + Similar to webdriver.find_elements(). + If "limit" is set and > 0, will only return that many elements. @Params driver - the webdriver object (required) selector - the locator for identifying the page element (required) by - the type of selector being used (Default: "css selector") + limit - the maximum number of elements to return if > 0. """ elements = driver.find_elements(by=by, value=selector) + if limit and limit > 0 and len(elements) > limit: + elements = elements[:limit] try: v_elems = [element for element in elements if element.is_displayed()] return v_elems except (StaleElementReferenceException, ElementNotInteractableException): time.sleep(0.1) elements = driver.find_elements(by=by, value=selector) + extra_elements = [] + if limit and limit > 0 and len(elements) > limit: + elements = elements[:limit] + extra_elements = elements[limit:] v_elems = [] for element in elements: if element.is_displayed(): v_elems.append(element) + if extra_elements and limit and len(v_elems) < limit: + for element in extra_elements: + if element.is_displayed(): + v_elems.append(element) + if len(v_elems) >= limit: + break return v_elems diff --git a/setup.py b/setup.py index 36c9dee8dfe..1b6ba8086ea 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ 'urllib3>=1.26.18,<2;python_version<"3.10"', 'urllib3>=1.26.18,<2.3.0;python_version>="3.10"', 'requests==2.31.0', - "pynose==1.4.8", + "pynose==1.5.0", 'sniffio==1.3.0', 'h11==0.14.0', 'outcome==1.3.0.post0', @@ -177,7 +177,7 @@ 'trio-websocket==0.11.1', 'wsproto==1.2.0', 'selenium==4.11.2;python_version<"3.8"', - 'selenium==4.17.2;python_version>="3.8"', + 'selenium==4.18.1;python_version>="3.8"', 'cssselect==1.2.0', "sortedcontainers==2.4.0", 'fasteners==0.19', @@ -225,7 +225,7 @@ # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ 'coverage==7.2.7;python_version<"3.8"', - 'coverage==7.4.1;python_version>="3.8"', + 'coverage==7.4.2;python_version>="3.8"', 'pytest-cov==4.1.0', ], # pip install -e .[flake8] @@ -251,7 +251,7 @@ 'pdfminer.six==20221105;python_version<"3.8"', 'pdfminer.six==20231228;python_version>="3.8"', 'cryptography==39.0.2;python_version<"3.9"', - 'cryptography==42.0.3;python_version>="3.9"', + 'cryptography==42.0.4;python_version>="3.9"', 'cffi==1.15.1;python_version<"3.8"', 'cffi==1.16.0;python_version>="3.8"', "pycparser==2.21",