From 7eb6efe87e742efdcd5c1a97242724a313102573 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:24:30 -0400 Subject: [PATCH 01/14] Add way to generate multiple Presenter slides from one --- seleniumbase/fixtures/base_case.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index ba42cea0264..ab18a5f0c2d 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -10892,7 +10892,31 @@ def add_slide( html += "%s%s" % (add_line, content2) html += '\n' % notes html += "\n\n" - self._presentation_slides[name].append(html) + if "" not in html and "" not in html: + self._presentation_slides[name].append(html) + else: + # Generate multiple slides with and + replacements = False + for num in range(32): + if "" % num in html and "" % num in html: + replacements = True + new_html = html + new_html = new_html.replace("" % num, "") + new_html = new_html.replace("" % num, "") + for num2 in range(32): + if num2 == num: + continue + if "" % num2 not in new_html and num2 >= 2: + break + new_html = new_html.replace("" % num2, "") + new_html = new_html.replace("" % num2, "") + self._presentation_slides[name].append(new_html) + else: + if num >= 2: + break + if not replacements: + # A is missing a closing tag. Do one. + self._presentation_slides[name].append(html) def save_presentation( self, name=None, filename=None, show_notes=False, interval=0 From f3e8e45521700cad900c39bb8d3e6b408dbeb154 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:25:38 -0400 Subject: [PATCH 02/14] driver.uc_click(selector) needs a backup JS option --- seleniumbase/core/browser_launcher.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 86ef44967d9..750b465a122 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -10,6 +10,7 @@ import urllib3 import warnings from selenium import webdriver +from selenium.common.exceptions import ElementClickInterceptedException from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common.options import ArgOptions from selenium.webdriver.common.service import utils as service_utils @@ -429,7 +430,10 @@ def uc_click( driver, selector, by="css selector", timeout=settings.SMALL_TIMEOUT ): element = driver.wait_for_element(selector, by=by, timeout=timeout) - element.uc_click() + try: + element.uc_click() + except ElementClickInterceptedException: + driver.js_click(selector, by=by, timeout=timeout) def edgedriver_on_path(): From c44dea2d8102f3075e4649e68e2387c4bf3a80dd Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:26:59 -0400 Subject: [PATCH 03/14] Refactor code for better compatibility with Appium --- seleniumbase/fixtures/base_case.py | 109 +++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index ab18a5f0c2d..02d937701ee 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -406,7 +406,11 @@ def click( self.__demo_mode_highlight_if_active(original_selector, original_by) if scroll and not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) try: if ( @@ -707,7 +711,11 @@ def double_click(self, selector, by="css selector", timeout=None): timeout=timeout, original_selector=original_selector, ) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass try: if self.browser == "safari": # Jump to the "except" block where the other script should work @@ -788,7 +796,11 @@ def context_click(self, selector, by="css selector", timeout=None): timeout=timeout, original_selector=original_selector, ) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass try: if self.browser == "safari": # Jump to the "except" block where the other script should work @@ -913,7 +925,12 @@ def update_text( except Exception: pass # Clearing the text field first might not be necessary self.__demo_mode_pause_if_active(tiny=True) - pre_action_url = self.driver.current_url + pre_action_url = None + if self.demo_mode: + try: + pre_action_url = self.driver.current_url + except Exception: + pass text = self.__get_type_checked_text(text) try: if not text.endswith("\n"): @@ -1011,7 +1028,12 @@ def add_text(self, selector, text, by="css selector", timeout=None): self.__demo_mode_highlight_if_active(selector, by) if not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) - pre_action_url = self.driver.current_url + pre_action_url = None + if self.demo_mode: + try: + pre_action_url = self.driver.current_url + except Exception: + pass text = self.__get_type_checked_text(text) try: if not text.endswith("\n"): @@ -1266,11 +1288,18 @@ def go_back(self): self.__check_scope() if hasattr(self, "recorder_mode") and self.recorder_mode: self.save_recorded_actions() - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass self.__last_page_load_url = None self.driver.back() - if pre_action_url == self.driver.current_url: - self.driver.back() # Again because the page was redirected + try: + if pre_action_url == self.driver.current_url: + self.driver.back() # Again because the page was redirected + except Exception: + pass if self.recorder_mode: time_stamp = self.execute_script("return Date.now();") origin = self.get_origin() @@ -1529,8 +1558,6 @@ def click_link_text(self, link_text, timeout=None): timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) - pre_action_url = self.driver.current_url - pre_window_count = len(self.driver.window_handles) link_text = self.__get_type_checked_text(link_text) if self.browser == "safari": if self.demo_mode: @@ -1558,7 +1585,12 @@ def click_link_text(self, link_text, timeout=None): return if not self.is_link_text_present(link_text): self.wait_for_link_text_present(link_text, timeout=timeout) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass + pre_window_count = len(self.driver.window_handles) try: element = self.wait_for_link_text_visible(link_text, timeout=0.2) self.__demo_mode_highlight_if_active(link_text, by="link text") @@ -1654,7 +1686,11 @@ def click_partial_link_text(self, partial_link_text, timeout=None): self.wait_for_partial_link_text_present( partial_link_text, timeout=timeout ) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) try: element = self.wait_for_partial_link_text( @@ -2125,7 +2161,11 @@ def click_visible_elements( except Exception: pass elements = self.find_elements(selector, by=by) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) click_count = 0 for element in elements: @@ -2207,7 +2247,11 @@ def click_nth_visible_element( if number < 0: number = 0 element = elements[number] - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) try: self.__scroll_to_element(element) @@ -2261,7 +2305,11 @@ def click_if_visible(self, selector, by="css selector", timeout=0): def click_active_element(self): self.wait_for_ready_state_complete() - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) if self.recorder_mode: selector = js_utils.get_active_element_css(self.driver) @@ -2593,7 +2641,11 @@ def hover_and_click( ) self.__demo_mode_highlight_if_active(original_selector, original_by) self.scroll_to(hover_selector, by=hover_by) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) if self.recorder_mode and self.__current_url_is_recordable(): if self.get_session_storage_item("pause_recorder") == "no": @@ -2719,7 +2771,11 @@ def hover_and_double_click( ) self.__demo_mode_highlight_if_active(original_selector, original_by) self.scroll_to(hover_selector, by=hover_by) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) outdated_driver = False element = None @@ -2906,7 +2962,11 @@ def __select_option( self.__demo_mode_highlight_if_active( dropdown_selector, dropdown_by ) - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) try: if option_by == "index": @@ -5802,7 +5862,11 @@ def js_click( css_selector = self.__escape_quotes_if_needed(css_selector) time_stamp = 0 action = ["", "", "", time_stamp] - pre_action_url = self.driver.current_url + pre_action_url = None + try: + pre_action_url = self.driver.current_url + except Exception: + pass pre_window_count = len(self.driver.window_handles) if self.recorder_mode and not self.__dont_record_js_click: time_stamp = self.execute_script("return Date.now();") @@ -6684,7 +6748,12 @@ def choose_file( self.__demo_mode_highlight_if_active(selector, by) if not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) - pre_action_url = self.driver.current_url + pre_action_url = None + if self.demo_mode: + try: + pre_action_url = self.driver.current_url + except Exception: + pass if self.recorder_mode and self.__current_url_is_recordable(): if self.get_session_storage_item("pause_recorder") == "no": time_stamp = self.execute_script("return Date.now();") From b16cf5e6ec3041e7e2f9a6b401b9ea9e76efc1ed Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:27:54 -0400 Subject: [PATCH 04/14] Update the default UC Mode reconnect time --- seleniumbase/fixtures/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index ee9c41b00b6..ee25c7dd16a 100644 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -360,7 +360,7 @@ class Mobile: class UC: - RECONNECT_TIME = 2.15 # Seconds + RECONNECT_TIME = 2.27 # Seconds class ValidBrowsers: From e80fb37fbf20cad00667192746b59f8118b8e0bf Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:29:24 -0400 Subject: [PATCH 05/14] Update the sample proxy list --- seleniumbase/config/proxy_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/seleniumbase/config/proxy_list.py b/seleniumbase/config/proxy_list.py index e8b5cfa269f..cdc7d923329 100644 --- a/seleniumbase/config/proxy_list.py +++ b/seleniumbase/config/proxy_list.py @@ -23,9 +23,9 @@ """ PROXY_LIST = { - "example1": "138.199.48.1:8443", # (Example) - set your own proxy here - "example2": "socks4://167.71.100.140:55229", # (Example) - "example3": "socks5://167.172.159.43:49633", # (Example) + "example1": "37.19.220.129:8443", # (Example) - set your own proxy here + "example2": "socks4://104.236.32.53:8915", # (Example) + "example3": "socks5://142.44.212.57:30439", # (Example) "proxy1": None, "proxy2": None, "proxy3": None, From c2eb2df43803af76c567c0286d449cdeecdef66d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:31:51 -0400 Subject: [PATCH 06/14] Update the documentation --- examples/presenter/ReadMe.md | 2 -- help_docs/desired_capabilities.md | 7 +++---- help_docs/happy_customers.md | 1 + help_docs/mysql_installation.md | 2 +- help_docs/syntax_formats.md | 16 ++++++++-------- help_docs/using_safari_driver.md | 2 +- help_docs/verify_webdriver.md | 1 - 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/examples/presenter/ReadMe.md b/examples/presenter/ReadMe.md index 16d7783d805..71ba8a7c619 100644 --- a/examples/presenter/ReadMe.md +++ b/examples/presenter/ReadMe.md @@ -36,7 +36,6 @@ cd examples/presenter pytest core_presentation.py ``` -

Creating a new presentation:

```python @@ -59,7 +58,6 @@ If creating multiple presentations at the same time, you can pass the ``name`` p Notes are disabled by default. You can enable notes by specifying: ``show_notes=True`` -

Adding a slide to a presentation:

```python diff --git a/help_docs/desired_capabilities.md b/help_docs/desired_capabilities.md index 7f69f038e20..3f3f8dc1dea 100644 --- a/help_docs/desired_capabilities.md +++ b/help_docs/desired_capabilities.md @@ -41,7 +41,7 @@ capabilities = { } ``` -(Note that the browser is now being specified in the capabilities file, rather than with ``--browser=BROWSER`` when using a **remote** Selenium Grid. If using a **local** Selenium Grid, specify the browser, eg: ``--browser=chrome`` or ``--browser=firefox``.) +(Note that the browser is now being specified in the capabilities file, rather than with ``--BROWSER`` when using a **remote** Selenium Grid. If using a **local** Selenium Grid, specify the browser, eg: ``--firefox``.)
You can generate specific desired capabilities using:
@@ -65,7 +65,7 @@ caps['KEY'] = False (Each pair must be on a separate line. You can interchange single and double quotes.) -You can also swap ``--browser=remote`` with an actual browser, eg ``--browser=chrome``, which will combine the default SeleniumBase desired capabilities with those that were specified in the capabilities file when using ``--cap_file=FILE.py``. Capabilities will override other parameters, so if you set the browser to one thing and the capabilities browser to another, SeleniumBase will use the capabilities browser as the browser. +You can also swap ``--browser=remote`` with an actual browser, eg ``--browser=chrome``, which will combine the default SeleniumBase desired capabilities with those that were specified in the capabilities file when using ``--cap_file=FILE.py``. Capabilities will override other parameters, so if you set the browser to one thing and the capabilities browser to another, SeleniumBase will use the capabilities browser. You'll need default SeleniumBase capabilities for: * Using a proxy server (not the same as a Selenium Grid server) @@ -74,8 +74,7 @@ You'll need default SeleniumBase capabilities for: * Overriding a website's Content Security Policy on Chrome * Other possible reasons -You can also set browser desired capabilities from a command line string: -Example: +You can also set browser desired capabilities from a command-line string. Eg: ```bash pytest test_swag_labs.py --cap-string='{"browserName":"chrome","name":"test1"}' --server="127.0.0.1" --browser=remote diff --git a/help_docs/happy_customers.md b/help_docs/happy_customers.md index dbbb6acff76..b46ea3b9e1e 100644 --- a/help_docs/happy_customers.md +++ b/help_docs/happy_customers.md @@ -6,6 +6,7 @@ * [MIT](https://web.mit.edu/) * [Sony](https://www.sony.com/) +* [Tesla](https://www.tesla.com/) * [iboss](https://www.iboss.com/) * [Apple](https://www.apple.com/) * [Akamai](https://www.akamai.com/) diff --git a/help_docs/mysql_installation.md b/help_docs/mysql_installation.md index 70ec7eea7ef..143b8465f41 100644 --- a/help_docs/mysql_installation.md +++ b/help_docs/mysql_installation.md @@ -87,7 +87,7 @@ Update your [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/mast ### Have SeleniumBase tests write to your MySQL DB: -Add the ``--with-db_reporting`` argument on the command line when you want tests to write to your MySQL database. Example: +Add the ``--with-db_reporting`` argument on the command-line when you want tests to write to your MySQL database. Example: ```bash pytest --with-db_reporting diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md index 190645d1a92..ea04283d0c8 100644 --- a/help_docs/syntax_formats.md +++ b/help_docs/syntax_formats.md @@ -118,10 +118,10 @@ The pytest framework comes with a unique system called fixtures, which replaces ```python def test_sb_fixture_with_no_class(sb): - sb.open("https://google.com/ncr") - sb.type('[title="Search"]', 'SeleniumBase\n') - sb.click('a[href*="github.com/seleniumbase/SeleniumBase"]') - sb.click('a[title="seleniumbase"]') + sb.open("seleniumbase.io/help_docs/install/") + sb.type('input[aria-label="Search"]', "GUI Commander") + sb.click('mark:contains("Commander")') + sb.assert_title_contains("GUI / Commander") ``` (See the top of examples/test_sb_fixture.py for the test.) @@ -134,10 +134,10 @@ The sb pytest fixture can also be used inside of a c ```python class Test_SB_Fixture: def test_sb_fixture_inside_class(self, sb): - sb.open("https://google.com/ncr") - sb.type('[title="Search"]', 'SeleniumBase\n') - sb.click('a[href*="github.com/seleniumbase/SeleniumBase"]') - sb.click('a[title="examples"]') + sb.open("seleniumbase.io/help_docs/install/") + sb.type('input[aria-label="Search"]', "GUI Commander") + sb.click('mark:contains("Commander")') + sb.assert_title_contains("GUI / Commander") ``` (See the bottom of examples/test_sb_fixture.py for the test.) diff --git a/help_docs/using_safari_driver.md b/help_docs/using_safari_driver.md index df2cf282e6b..e2e77be56dd 100644 --- a/help_docs/using_safari_driver.md +++ b/help_docs/using_safari_driver.md @@ -8,4 +8,4 @@ You can find the official Apple documentation regarding "Testing with WebDriver Run ``safaridriver --enable`` once in a terminal to enable Safari's WebDriver. (If you’re upgrading from a previous macOS release, you may need to prefix the command with ``sudo``.) -Now you can use ``--browser=safari`` to run your **SeleniumBase** tests on Safari. +Now you can use ``--safari`` to run your **SeleniumBase** tests on Safari. diff --git a/help_docs/verify_webdriver.md b/help_docs/verify_webdriver.md index c8abc02c761..083e5d4b6e1 100644 --- a/help_docs/verify_webdriver.md +++ b/help_docs/verify_webdriver.md @@ -8,7 +8,6 @@ Drivers can be manually downloaded to the ``seleniumbase/drivers`` folder with c ```bash sbase get chromedriver -sbase get chromedriver latest sbase get geckodriver sbase get edgedriver ``` From 7678d8ea20e9e2005840ffc6aab4c8c2319942eb Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:33:13 -0400 Subject: [PATCH 07/14] Update existing presentations --- examples/presenter/edge_presentation.py | 158 ++++-------------------- examples/presenter/fundamentals.py | 151 ++++------------------ 2 files changed, 52 insertions(+), 257 deletions(-) diff --git a/examples/presenter/edge_presentation.py b/examples/presenter/edge_presentation.py index c90eb375c7c..80df3ff1ee3 100644 --- a/examples/presenter/edge_presentation.py +++ b/examples/presenter/edge_presentation.py @@ -134,8 +134,12 @@ def test_presentation(self): self.highlight( 'img[srcset*="logo"] + div span:nth-of-type(2)', loops=16 ) - self.highlight('span[aria-live="assertive"]', loops=8) + if self.is_element_visible('span[aria-live="assertive"]'): + self.highlight('span[aria-live="assertive"]', loops=8) + elif self.is_element_visible('a[href*="fwlink"]'): + self.highlight('a[href*="fwlink"]', loops=8) self.highlight('a[href*="chromium"]') + self.highlight('a[href*="credits"]') self.quit_extra_driver() self.switch_to_default_driver() @@ -231,96 +235,16 @@ def test_presentation(self): "

What are some building blocks?

\n" "

\n", code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" + "from selenium import webdriver\n\n" + "driver = webdriver.Edge()\n\n" + 'driver.get("http://selenium.dev")\n\n' + "element = driver.find_element" '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" + "element.click()\n\n" + "elem_2 = driver.find_element" '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

What are some building blocks?

\n" - "

\n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

What are some building blocks?

\n" - "

\n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

What are some building blocks?

\n" - "

\n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

What are some building blocks?

\n" - "

\n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

What are some building blocks?

\n" - "

\n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Edge()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" + 'elem_2.send_keys("Python")
\n\n' + "driver.quit()\n\n" ), ) self.add_slide( @@ -369,31 +293,17 @@ def test_presentation(self): "without extra libraries or frameworks?


" "


\n" "The command statements can get a bit too long:

\n" - "

" + "

" "driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()" - "


" + "


" "

This is better:

" - "

self.click(CSS_SELECTOR)


", + "

self.click(CSS_SELECTOR)


", ) self.add_slide( "

What are some disadvantages of using raw Selenium " - "without extra libraries or frameworks?


" - "


\n" - "The command statements can get a bit too long:

\n" - "

" - "driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()" - "


" - "

This is better:

" - "

self.click(CSS_SELECTOR)


", - ) - self.add_slide( - "

What are some disadvantages of using raw Selenium " - "without extra libraries or frameworks?


" - "
\n" - "No HTML reports, dashboards, screenshots..." - "
" - "

A test framework can provide those!

" - "
", + "without extra libraries or frameworks?



\n" + "No HTML reports, dashboards, screenshots...
" + "

A test framework can provide those!


", ) self.add_slide( "
Raw Selenium disadvantages, continued...

" @@ -402,34 +312,16 @@ def test_presentation(self): image="https://seleniumbase.io/cdn/img/dash_report.png", ) self.add_slide( - "

Raw Selenium disadvantages, continued...


\n" - "
\n" - "

It takes multiple lines of code to do simple tasks:" - "

\n" - "
\n"
+            "

Raw Selenium disadvantages, continued...


\n
\n" + "

It takes multiple lines of code to do simple tasks:" + "

\n
\n"
             'element = driver.find_element("css selector", "#password")\n'
             "element.clear()\n"
             'element.send_keys("secret_sauce")\n'
             'element.submit()\n'
-            "
\n" - "
\n" - "

But with a framework, do all that in ONE line:

\n" - '
self.type("#password", "secret_sauce\\n")
' - ) - self.add_slide( - "

Raw Selenium disadvantages, continued...


\n" - "
\n" - "

It takes multiple lines of code to do simple tasks:

\n" - "
\n"
-            'element = driver.find_element("css selector", "#password")\n'
-            "element.clear()\n"
-            'element.send_keys("secret_sauce")\n'
-            'element.submit()\n'
-            "
\n" - "
\n" - "

But with a framework, do all that in ONE line:" - "

\n" - '
self.type("#password", "secret_sauce\\n")
' + "
\n
\n" + "

But with a framework, do all that in ONE line:" + '

\n
self.type("#password", "secret_sauce\\n")
' ) self.add_slide( "

What else can test frameworks provide?


\n" diff --git a/examples/presenter/fundamentals.py b/examples/presenter/fundamentals.py index dede768c4b0..e5c12df77bf 100644 --- a/examples/presenter/fundamentals.py +++ b/examples/presenter/fundamentals.py @@ -1,4 +1,5 @@ from seleniumbase import BaseCase +BaseCase.main(__name__, __file__) class MyTestClass(BaseCase): @@ -23,14 +24,14 @@ def test_presentation(self): "
  • Conference organizers (made today possible)

  • " "
  • My wife (a major supporter of my work)

  • " "
  • iboss (my employer)
  • \n" - "\n", + "", ) self.add_slide( "

    About me:

    \n" "\n", + "", image="https://seleniumbase.io/other/iboss_booth.png", ) self.add_slide( @@ -51,7 +52,7 @@ def test_presentation(self): "
    \n" "
  • How SeleniumBase makes Python Web Automation easier.
  • " "
    \n" - "\n", + "", ) self.add_slide( "

    The Format:

    " @@ -60,7 +61,7 @@ def test_presentation(self): "
  • Slides.
  • \n" "
  • ReadMe files.
  • \n" "
  • LOTS of live demos!!!
  • \n" - "\n", + "", image="https://seleniumbase.io/other/presentation_parts.png", ) self.add_slide( @@ -81,96 +82,16 @@ def test_presentation(self): "

    What are some building blocks?

    \n" "

    \n", code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" + "from selenium import webdriver\n\n" + "driver = webdriver.Edge()\n\n" + 'driver.get("http://selenium.dev")\n\n' + "element = driver.find_element" '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" + "element.click()\n\n" + "elem_2 = driver.find_element" '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

    What are some building blocks?

    \n" - "

    \n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

    What are some building blocks?

    \n" - "

    \n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

    What are some building blocks?

    \n" - "

    \n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

    What are some building blocks?

    \n" - "

    \n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" - ), - ) - self.add_slide( - "

    What are some building blocks?

    \n" - "

    \n", - code=( - "from selenium import webdriver\n\n" - "driver = webdriver.Chrome()\n\n" - 'driver.get("http://selenium.dev")\n\n' - "element = driver.find_element" - '("css selector", "#docsearch span")\n\n' - "element.click()\n\n" - "elem_2 = driver.find_element" - '("css selector", "#docsearch-input")\n\n' - 'elem_2.send_keys("Python")\n\n' - "driver.quit()\n\n" + 'elem_2.send_keys("Python")
    \n\n' + "driver.quit()\n\n" ), ) self.add_slide( @@ -219,55 +140,38 @@ def test_presentation(self): ) self.add_slide( "

    What are some disadvantages of using raw Selenium " - "without additional libraries or frameworks?


    " + "without extra libraries or frameworks?
    " "


    \n" "The command statements can get a bit too long:

    \n" - "

    " + "

    " "driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()" - "


    " + "


    " "

    This is better:

    " - "

    self.click(CSS_SELECTOR)


    ", - ) - self.add_slide( - "

    What are some disadvantages of using raw Selenium " - "without additional libraries or frameworks?


    " - "


    \n" - "The command statements can get a bit too long:

    \n" - "

    " - "driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()" - "


    " - "

    This is better:

    " - "

    self.click(CSS_SELECTOR)


    ", + "

    self.click(CSS_SELECTOR)


    ", ) self.add_slide( "

    What are some disadvantages of using raw Selenium " "without additional libraries or frameworks?


    " "


    \n" "No HTML reports, dashboards, results, screenshots..." - "


    " - "A test framework can provide those!" - "
    ", + "

    A test framework can provide those!
    ", ) self.add_slide( - "

    Raw Selenium disadvantages, continued...

    " - "

    \n" - "No HTML reports, dashboards, results, screenshots..." - "
    " - "A test framework can provide those!", + "

    Raw Selenium disadvantages, continued...
    \n" + "No HTML reports, dashboards, results, screenshots...
    " + "A test framework can provide those!
    ", image="https://seleniumbase.io/cdn/img/dash_report.png", ) self.add_slide( "

    Raw Selenium disadvantages, continued...


    \n" "
    \n" "

    It takes multiple lines of code to do simple tasks:" - "

    \n" - "
    \n"
    +            "

    \n
    \n"
                 'element = driver.find_element("css selector", "#password")\n'
                 "element.clear()\n"
                 'element.send_keys("secret_sauce")\n'
                 'element.submit()\n'
    -            "
    \n" - "
    \n" + "
    \n
    \n" "

    But with a framework, do all that in ONE line:

    \n" '
    self.type("#password", "secret_sauce\\n")
    ' ) @@ -280,8 +184,7 @@ def test_presentation(self): "element.clear()\n" 'element.send_keys("secret_sauce")\n' 'element.submit()\n' - "\n" - "
    \n" + "\n
    \n" "

    But with a framework, do all that in ONE line:" "

    \n" '
    self.type("#password", "secret_sauce\\n")
    ' @@ -299,7 +202,7 @@ def test_presentation(self): "
  • Advanced tools (Eg. test recorders)
  • \n" "
  • Easy to read error messages. Eg. " '
    Element "h2" was not visible after 10s!
  • ' - "\n", + "", ) self.add_slide( "

    What about test runners?


    \n" @@ -316,7 +219,7 @@ def test_presentation(self): "
  • Provide test assertions.
  • \n" "
  • Multithread your tests.
  • \n" "
  • Use a large number of existing plugins.
  • \n" - "\n", + "", ) self.add_slide( "

    What about complete frameworks?


    \n" @@ -339,7 +242,7 @@ def test_presentation(self): "
  • Advanced tools (Eg. test recorders)
  • \n" "
  • Easy to read error messages. Eg. " '
    Element "h2" was not visible after 10s!
  • ' - "\n", + "", ) self.add_slide( "

    How do you get SeleniumBase?

    \n" From 54496141922d304321d99dbfd733a53b53010c62 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:34:25 -0400 Subject: [PATCH 08/14] Update example tests --- examples/edge_test.py | 6 +++++- examples/presenter/multi_uc.py | 23 +++++++++++++++++++++++ examples/test_repeat_tests.py | 23 ++++++++++++++--------- 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 examples/presenter/multi_uc.py diff --git a/examples/edge_test.py b/examples/edge_test.py index 1c42541b8a8..af16163228b 100644 --- a/examples/edge_test.py +++ b/examples/edge_test.py @@ -23,5 +23,9 @@ def test_edge(self): self.assert_text("Microsoft Edge", 'img[srcset*="logo"] + div') self.highlight('img[srcset*="logo"] + div span:nth-of-type(1)') self.highlight('img[srcset*="logo"] + div span:nth-of-type(2)') - self.highlight('span[aria-live="assertive"]') + if self.is_element_visible('span[aria-live="assertive"]'): + self.highlight('span[aria-live="assertive"]', loops=8) + elif self.is_element_visible('a[href*="fwlink"]'): + self.highlight('a[href*="fwlink"]', loops=8) self.highlight('a[href*="chromium"]') + self.highlight('a[href*="credits"]') diff --git a/examples/presenter/multi_uc.py b/examples/presenter/multi_uc.py new file mode 100644 index 00000000000..d1107cec3ab --- /dev/null +++ b/examples/presenter/multi_uc.py @@ -0,0 +1,23 @@ +"""Part of the UC presentation""" +import pytest +from random import randint +from seleniumbase import BaseCase +BaseCase.main(__name__, __file__, "--uc", "-n3") + + +@pytest.mark.parametrize("", [[]] * 3) +def test_multi_threaded(sb): + sb.driver.get("https://nowsecure.nl/#relax") + sb.set_window_rect(randint(0, 755), randint(38, 403), 700, 500) + try: + sb.assert_text("OH YEAH, you passed!", "h1", timeout=4) + sb.post_message("Selenium wasn't detected!", duration=4) + sb._print("\n Success! Website did not detect Selenium! ") + except Exception: + sb.driver.get("https://nowsecure.nl/#relax") + try: + sb.assert_text("OH YEAH, you passed!", "h1", timeout=4) + sb.post_message("Selenium wasn't detected!", duration=4) + sb._print("\n Success! Website did not detect Selenium! ") + except Exception: + sb.fail('Selenium was detected! Try using: "pytest --uc"') diff --git a/examples/test_repeat_tests.py b/examples/test_repeat_tests.py index 94511ec999c..6c696a29b80 100644 --- a/examples/test_repeat_tests.py +++ b/examples/test_repeat_tests.py @@ -6,25 +6,30 @@ from parameterized import parameterized from seleniumbase import BaseCase +url = "data:text/html,

    Hello

     

    " + class RepeatTests(BaseCase): @parameterized.expand([[]] * 2) def test_repeat_this_test_with_parameterized(self): - self.open("seleniumbase.github.io/") - self.click('a[href="help_docs/method_summary/"]') - self.assert_text("API Reference", "h1") + self.open(url) + self.type("input", "SeleniumBase is fun") + self.click('button:contains("OK!")') + self.assert_text("Hello", "h2") @pytest.mark.parametrize("", [[]] * 2) def test_repeat_this_test_with_pytest_parametrize(sb): - sb.open("seleniumbase.github.io/") - sb.click('a[href="seleniumbase/console_scripts/ReadMe/"]') - sb.assert_text("Console Scripts", "h1") + sb.open(url) + sb.type("input", "SeleniumBase is fun") + sb.click('button:contains("OK!")') + sb.assert_text("Hello", "h2") class RepeatTestsWithPytest: @pytest.mark.parametrize("", [[]] * 2) def test_repeat_test_with_pytest_parametrize(self, sb): - sb.open("seleniumbase.github.io/") - sb.click('a[href="help_docs/customizing_test_runs/"]') - sb.assert_text("Command Line Options", "h1") + sb.open(url) + sb.type("input", "SeleniumBase is fun") + sb.click('button:contains("OK!")') + sb.assert_text("Hello", "h2") From 45aba2aaea0efa20b0a68e11eca31dcd27279b16 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:34:58 -0400 Subject: [PATCH 09/14] Update Presenter documentation --- examples/presenter/ReadMe.md | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/examples/presenter/ReadMe.md b/examples/presenter/ReadMe.md index 71ba8a7c619..cf5decb4ff4 100644 --- a/examples/presenter/ReadMe.md +++ b/examples/presenter/ReadMe.md @@ -241,6 +241,45 @@ Presentations automatically get saved when calling: self.begin_presentation(show_notes=True) ``` +

    Special abilities:

    + +If you want to highlight multiple lines at different times in the same slide with the `` / `` tags, you can use the new ``-``, ``-`` tags, which will generate multiple HTML slides from one Python slide. + +Example: + +```python +self.add_slide( + code=( +

    Highlight this on the 1st generated slide

    +

    Highlight this on the 2nd generated slide

    +

    Highlight this on the 3rd generated slide

    +

    Highlight this on the 4th generated slide

    + ) +) +``` + +Those should automatically get converted to `` ... `` on their turn: + +Eg. First generated slide: + +```html +

    Highlight this on the first generated slide

    +

    Highlight this on the second generated slide

    +

    Highlight this on the third generated slide

    +

    Highlight this on the fourth generated slide>

    +``` + +Eg. Second generated slide: + +```html +

    Highlight this on the first generated slide

    +

    Highlight this on the second generated slide

    +

    Highlight this on the third generated slide

    +

    Highlight this on the fourth generated slide>

    +``` + +Etc... + --------

    SeleniumBase

    From 4125ff552d25bbdf6065ebc94afd59cb3ee4ca77 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:36:01 -0400 Subject: [PATCH 10/14] Add the UC Presentation --- examples/presenter/uc_presentation.py | 568 ++++++++++++++++++++++++++ 1 file changed, 568 insertions(+) create mode 100644 examples/presenter/uc_presentation.py diff --git a/examples/presenter/uc_presentation.py b/examples/presenter/uc_presentation.py new file mode 100644 index 00000000000..a94aee7a76e --- /dev/null +++ b/examples/presenter/uc_presentation.py @@ -0,0 +1,568 @@ +import os +import subprocess +from seleniumbase import BaseCase +from seleniumbase import SB +BaseCase.main(__name__, __file__) + + +class UCPresentationClass(BaseCase): + def test_presentation(self): + self.open("data:,") + self.create_presentation(theme="beige", transition="fade") + self.add_slide( + "

    A deep dive into undetectable automation, with:

    " + "
    SeleniumBase UC Mode" + " and undetected-chromedriver
    ", + image="https://seleniumbase.io/cdn/img/uc_mode_phases_3.png", + ) + self.add_slide( + '' + ) + self.add_slide( + "

    πŸ”Ή The Objective πŸ”Ή



    " + "

    By the end of this presentation, you'll learn how to" + " create bots that appear as humans to websites.


    " + "

    (These bots won't get detected or blocked.)

    " + "

    Here's a live demo of that...

    " + ) + self.begin_presentation(filename="uc_presentation.html") + + self.get_new_driver(undetectable=True) + try: + self.driver.get("https://nowsecure.nl/#relax") + try: + self.assert_text("OH YEAH, you passed!", "h1", timeout=4) + self.post_message("Selenium wasn't detected!", duration=4) + except Exception: + self.clear_all_cookies() + self.driver.get("https://nowsecure.nl/#relax") + self.assert_text("OH YEAH, you passed!", "h1", timeout=4) + self.post_message("Selenium wasn't detected!", duration=4) + finally: + self.quit_extra_driver() + + if os.path.exists("multi_uc.py"): + self.create_presentation(theme="beige", transition="fade") + self.add_slide( + "

    πŸ”Ή There's a lot more to come! πŸ”Ή



    " + "

    If one bot isn't enough, how about several?" + "



    " + "

    Here's a demo of multithreaded bots in parallel..." + "

    " + ) + self.begin_presentation(filename="uc_presentation.html") + subprocess.Popen("pytest multi_uc.py --uc -q -n3", shell=True) + self.sleep(6) + self.create_presentation(theme="serif", transition="fade") + self.add_slide( + "

    Not just an army of bots, but an army of bots
    " + "that look just like humans using web browsers.


    " + "

    (That's how they weren't detected!)

    " + ) + self.begin_presentation(filename="uc_presentation.html") + + self.create_presentation(theme="serif", transition="fade") + self.add_slide( + "If this is what you came here for, stick around
    to" + " learn how to do the things you just saw.
    " + "

    You may find it easier to build a Selenium
    " + "bot than to navigate an obstacle course.

    ", + image="https://seleniumbase.io/other/yeah_you_passed.png", + ) + self.add_slide( + "

    But first, you may be wondering who I am...

    " + "

    Or maybe you're one of over one million people
    " + "that I've already helped on Stack Overview:

    " + '' + ) + self.add_slide( + "

    About the presenter (Michael Mintz):

    \n" + "
      \n" + "
    • I created SeleniumBase (for Python).
    • \n" + "
    • I lead the Automation Team at iboss.
    • \n" + "
    \n", + image="https://seleniumbase.io/other/iboss_booth.png", + ) + self.add_slide( + "

    I've been doing video podcasts since 2012!

    " + "

    (That's when I first co-hosted the
    Marketing Update" + " on HubSpot TV)

    ", + image="https://seleniumbase.io/other/hub_tv.png", + ) + self.add_slide( + "

    I spoke at Selenium Conference 2023:

    " + "

    (As the dedicated Python Selenium speaker)

    ", + image="https://seleniumbase.io/other/me_se_conf.jpg", + ) + self.add_slide( + "

    Here's me with the creators
    " + "of Selenium / WebDriver:

    ", + image="https://seleniumbase.io/other/selenium_builders.jpg", + ) + self.add_slide( + "SeleniumBase Fun Fact:" + "
    The 1st SB GitHub issue was from a Tesla engineer:", + image="https://seleniumbase.io/other/first_issue.png", + ) + self.add_slide( + "

    Now, let me explain how we got here...

    " + "

    And by here, I mean a time when lots of companies" + " have been building services to detect and block bots:

    ", + image="https://seleniumbase.io/other/verify_human.png", + ) + self.add_slide( + "

    In the early days, there were few bots,
    " + "and those bots didn't look human at all.


    " + "

    Those early bots were mostly innocent, and
    " + "most websites didn't care if they were around.

    " + ) + self.add_slide( + "

    At some point, the number of bots grew by a lot...


    " + "

    Many bots were programmed with bad intentions...


    " + "

    And sometimes you couldn't tell apart the good bots" + " from the bad ones until it was already too late...

    ", + ) + self.add_slide( + "

    Then came Google reCAPTCHA v1...

    " + "

    Although intended as a defense against bots,
    " + " humans on a web browser also got hit by it.

    ", + image="https://seleniumbase.io/other/recaptcha_v1.png", + ) + self.add_slide( + "

    Then Google made improvements:

    " + "

    (reCAPTCHA v2 / reCAPTCHA v3)

    ", + image="https://seleniumbase.io/other/recaptcha_v2a.png", + ) + self.add_slide( + "

    And that annoyed a lot of people...

    ", + image="https://seleniumbase.io/other/recaptcha_v2b.png", + ) + self.add_slide( + "

    Then came the next iteration of anti-bot security:

    " + "

    Cloudflare Turnstile CAPTCHA-replacement." + "

    That changed the game in many ways.

    ", + image="https://seleniumbase.io/other/check_if_secure.png", + ) + self.add_slide( + "For awhile, Cloudflare's Turnstile did a decent
    " + "job filtering out bot traffic from human traffic." + "

    Then undetected-chromedriver arrived:

    ", + image="https://seleniumbase.io/other/undetected_ch.png", + ) + self.add_slide( + '' + ) + self.add_slide( + "

    undetected-chromedriver was found to be
    " + "incredibly effective at getting past the Turnstile:

    ", + image="https://seleniumbase.io/other/connection_secure.png", + ) + self.add_slide( + "

    The maintainer of undetected-chromedriver:

    " + "

    He appears to be very busy with various projects.

    " + '' + ) + self.add_slide( + "

    The biggest challenge for undetected-chromedriver" + " has been adapting to breaking changes caused by:

    " + "
      \n" + "
    • New versions of Chrome.
    • \n" + "
    • New versions of Selenium.
    • \n" + "
    • New versions of Cloudflare.
    • \n" + "


    \n" + "

    Those can brake things until updates are released:

    " + '' + ) + self.add_slide( + "

    Thankfully, undetected-chromedriver has
    " + "supporters helping to figure out and fix things.

    " + '' + ) + self.add_slide( + "

    Sometimes all that help isn't enough...

    " + "

    That's where seleniumbase UC Mode comes in:

    " + '' + ) + self.add_slide( + "

    SeleniumBase UC Mode is a modified fork of
    " + "undetected-chromedriver with multiple changes.

    " + "

    UC Mode includes bug fixes and additional features, such as" + " multithreading support via pytest-xdist.

    " + '' + ) + self.add_slide( + "

    UC Mode is one of many SeleniumBase modes:" + "

    \n
      \n" + "
    • UC Mode (--uc / uc=True)
    • \n" + "
    • Slow Mode (--slow)
    • \n" + "
    • Demo Mode (--demo)
    • \n" + '
    • Proxy Mode' + ' (--proxy="h:p"/"u:p@h:p")
    • \n' + "
    • Debug Mode " + "(--pdb/--trace/--ftrace)
    • \n" + "
    • Mobile Mode " + "(--mobile / mobile=True)
    • \n" + "
    • Recorder Mode " + "(--rec / --rec-behave)
    • \n" + "
    • Multithreaded Mode " + "(pytest -n4 / -n8)
    • \n" + "
    \n" + "

    And more! (You can even combine modes!)

    \n" + ) + self.add_slide( + "

    ℹ️: UC Mode is not enabled by default.
    " + " It must be activated by switching it on:

    " + "
      \n" + "
    • --uc  " + " (pytest command-line option)
    • \n" + "
    • uc=True  " + " (SB/driver manager formats)
    • \n" + "




    \n

    " + "Then websites can no longer detect chromedriver.

    " + ) + self.add_slide( + "

    Here's an example script that uses UC Mode:

    \n" + "
    (Note that SeleniumBase has driver methods" + "
    that aren't included with standard Selenium.)
    \n" + "

    \n", + code=( + "from seleniumbase import Driver\n\n" + "driver = Driver(uc=True)\n" + "try:\n" + ' driver.get("https://nowsecure.nl/#relax")\n' + " driver.sleep(4)\n" + " # DO MORE STUFF\n" + "finally:\n" + " driver.quit()\n" + ), + ) + self.add_slide( + "

    For reference, here's a script in a different
    " + "SeleniumBase format, with more methods:

    " + "
    (Get ready for another live demo...)
    ", + code=( + "from seleniumbase import SB\n\n" + "with SB(uc=True) as sb:\n" + ' sb.driver.get(' + '"https://seleniumbase.io/simple/login")\n' + ' sb.type("#username", "demo_user")\n' + ' sb.type("#password", "secret_pass")\n' + ' sb.click(\'a:contains("Sign in")\')\n' + ' sb.assert_exact_text("Welcome!", "h1")\n' + ' sb.assert_element("img#image1")\n' + ' sb.highlight("#image1")\n' + ' sb.click_link("Sign out")\n' + ' sb.assert_text("signed out", "#top_message")' + "\n" + ), + ) + self.begin_presentation(filename="uc_presentation.html") + + try: + with SB(uc=True) as sb: + sb.driver.get("https://seleniumbase.io/simple/login") + sb.type("#username", "demo_user") + sb.type("#password", "secret_pass") + 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") + except Exception: + pass + + self.create_presentation(theme="serif", transition="fade") + self.add_slide( + "

    Was that too fast for you?

    " + "

    Let's run that again in Demo Mode:

    ", + code=( + "from seleniumbase import SB\n\n" + "with SB(uc=True, demo=True) as sb:\n" + ' sb.driver.get(' + '"https://seleniumbase.io/simple/login")\n' + ' sb.type("#username", "demo_user")\n' + ' sb.type("#password", "secret_pass")\n' + ' sb.click(\'a:contains("Sign in")\')\n' + ' sb.assert_exact_text("Welcome!", "h1")\n' + ' sb.assert_element("img#image1")\n' + ' sb.highlight("#image1")\n' + ' sb.click_link("Sign out")\n' + ' sb.assert_text("signed out", "#top_message")\n' + ), + ) + self.begin_presentation(filename="uc_presentation.html") + + try: + with SB(uc=True, demo=True) as sb: + sb.driver.get("https://seleniumbase.io/simple/login") + sb.type("#username", "demo_user") + sb.type("#password", "secret_pass") + 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") + except Exception: + pass + + self.create_presentation(theme="serif", transition="fade") + self.add_slide( + "

    Note the differences between undetected-chromedriver and" + " SeleniumBase UC Mode.


    SeleniumBase UC Mode:" + "

      \n" + "
    • Has driver version-detection & management." + "
    • \n" + "
    • Allows mismatched browser/driver versions." + "
    • \n" + "
    • Changes the user agent to prevent detection." + "
    • \n" + "
    • Hides chromedriver from Chrome as needed." + "
    • \n" + "
    • Allows for multithreaded tests in parallel." + "
    • \n" + "
    • Adjusts configuration based on the environment." + "
    • \n" + "
    • Has options for proxy and proxy-with-auth." + "
    • \n
    \n" + ) + self.add_slide( + "

    Here's another UC Mode script:

    " + "

    (Get ready for another live demo...)

    " + "

    ", + code=( + "from seleniumbase import SB\n\n" + "with SB(uc=True) as sb:\n" + ' sb.driver.get("https://nowsecure.nl/#relax")\n' + " sb.sleep(1)\n" + ' if not sb.is_text_visible("OH YEAH, you passed", "h1"):\n' + " sb.get_new_driver(undetectable=True)\n" + ' sb.driver.get("https://nowsecure.nl/#relax")\n' + " sb.sleep(1)\n" + ' sb.activate_demo_mode()\n' + ' sb.assert_text("OH YEAH, you passed!", "h1", timeout=3)\n' + ), + ) + self.begin_presentation(filename="uc_presentation.html") + + try: + with SB(uc=True) as sb: + sb.driver.get("https://nowsecure.nl/#relax") + sb.sleep(1) + if not sb.is_text_visible("OH YEAH, you passed", "h1"): + sb.driver.get("https://nowsecure.nl/#relax") + sb.sleep(1) + sb.activate_demo_mode() + sb.assert_text("OH YEAH, you passed!", "h1", timeout=3) + except Exception: + pass + + self.create_presentation(theme="serif", transition="fade") + self.add_slide( + "

    Now let's learn how UC Mode works in general.

    " + "

    First, there are several things that need to happen for" + " browsers to remain undetected from anti-bot services.

    \n", + image="https://seleniumbase.io/other/yeah_you_passed.png", + ) + self.add_slide( + "Requirements for avoiding detection (UC Mode)
    " + "
      \n" + "
    • Modify chromedriver to rename driver variables" + " that appear in the Chrome DevTools console.
    • \n" + "
    • Launch Chrome before attaching chromedriver.
      " + "(Don't launch Chrome with chromedriver)
    • \n" + "
    • Don't use Selenium-specific Chrome options." + "
    • \n
    • If using headless Chrome, change" + " HeadlessChrome to Chrome in the User Agent.
    • \n" + "
    • If using a custom user_data_dir, don't let that" + " folder be used with non-UC-Mode Chrome.
    • \n" + "
    • Disconnect chromedriver briefly from Chrome before" + " loading websites with detection services.
    • \n" + "
    " + ) + self.add_slide( + "Requirements, continued... / Good news:
    \n" + "

    Most of those things are already done automatically" + " when using UC Mode with default settings.

    \n" + "

    The part that's your responsibility, (if setting a" + " custom user_data_dir), is making sure that" + " the u_d_d is only used by UC Mode Chrome instances. If you" + ' "cross the streams", UC Mode can be detected.

    ' + "

    (UC Mode takes care of the other requirements.)" + "

    " + ) + self.add_slide( + "

    With those things done, your bot can appear human.

    \n" + "
    But if anyone looks too closely at what your bot does,
    " + 'it may raise suspicion, even if already marked "not a bot".
    ', + image="https://seleniumbase.io/other/other_anti_bots.jpg", + ) + self.add_slide( + "

    There are additional methods that you can use" + " to have a better experience when using UC Mode:

    \n" + "

    Note that driver.get(url) has" + " been modified from the original to
    reconnect automatically" + " if a web page is using bot-detection software.
    " + "

    ", + code=( + "driver.default_get(url)\n\n" + "driver.uc_open(url)\n\n" + "driver.uc_open_with_tab(url)\n\n" + "driver.uc_open_with_reconnect(url, reconnect_time)\n\n" + 'driver.uc_click(selector, by="css selector", timeout=7)\n' + ), + ) + self.add_slide( + "

    There are additional methods that you can use" + " to have a better experience when using UC Mode:

    " + "

    Since driver.get(url) has" + " been modified, driver.default_get(url)" + " exists to do a regular get(url)," + " which may be useful if revisiting a website.
    " + "

    ", + code=( + "driver.default_get(url)\n\n" + "driver.uc_open(url)\n\n" + "driver.uc_open_with_tab(url)\n\n" + "driver.uc_open_with_reconnect(url, reconnect_time)\n\n" + 'driver.uc_click(selector, by="css selector", timeout=7)\n' + ), + ) + self.add_slide( + "

    There are additional methods that you can use" + " to have a better experience when using UC Mode:

    " + "

    driver.uc_open(url) will" + " open a URL in the same tab with a disconnect.
    " + "(This might not be enough to bypass detection.)
    " + "

    ", + code=( + "driver.default_get(url)\n\n" + "driver.uc_open(url)\n\n" + "driver.uc_open_with_tab(url)\n\n" + "driver.uc_open_with_reconnect(url, reconnect_time)\n\n" + 'driver.uc_click(selector, by="css selector", timeout=7)\n' + ), + ) + self.add_slide( + "

    There are additional methods that you can use" + " to have a better experience when using UC Mode:

    \n" + "

    driver.uc_open_with_tab(url)" + " opens a URL in a new tab with a disconnect. Similar to the new" + " driver.get(url), but without the pre-check." + "


    ", + code=( + "driver.default_get(url)\n\n" + "driver.uc_open(url)\n\n" + "driver.uc_open_with_tab(url)\n\n" + "driver.uc_open_with_reconnect(url, reconnect_time)\n\n" + 'driver.uc_click(selector, by="css selector", timeout=7)\n' + ), + ) + self.add_slide( + "

    There are additional methods that you can use" + " to have a better experience when using UC Mode:

    \n" + "

    driver.uc_open_with_tab(url) opens" + " a URL in a new tab with a disconnect. Similar to the new" + " driver.get(url), but without the pre-check." + "


    \n" + "
    As a reminder, the driver.get(url)" + " pre-check checks to see if a URL has bot-detection software" + " on it before opening the URL in a new tab with a disconnect." + "
    \n" + "
    This pre-check is done using" + " requests.get(URL)
    before opening" + " a URL in the UC Mode web browser.
    " + "
    If the response code is a" + ' "403" (Forbidden),
    ' + "then the URL is opened with a disconnect." + "
    " + ) + self.add_slide( + "

    Customizing the default disconnect/reconnect time

    " + "
    \n" + "

    Here's a method for a custom reconnect time
    " + "when opening a page that tries to detect bots:

    " + "
    driver.uc_open_with_reconnect(url, reconnect_time)"
    +            "
    " + "
    (The default reconnect_time is slightly more than 2 seconds.)" + "


    ", + code=( + "# Example:\n" + "driver.uc_open_with_reconnect(\n" + ' "https://nowsecure.nl/#relax", reconnect_time=6\n)' + "\n\n" + "# Short form example:\n" + "driver.uc_open_with_reconnect(" + '"https://nowsecure.nl/#relax", 6)\n' + ), + ) + self.add_slide( + "

    Clicking with a disconnect/reconnect


    \n" + "

    If your bot needs to click a button on a website that has" + " anti-bot services, you might be able to do it with this special" + " method, which forces a short disconnect:

    \n" + "
    driver.uc_click(selector)
    " + '
    (Defaults: by="css selector", timeout=7)
    ' + "

    \n", + code=( + "# Examples:\n" + 'driver.uc_click("button")\n\n' + 'driver.uc_click("button#id", timeout=10)\n' + ), + ) + self.add_slide( + "

    Links to UC Mode code



    \n", + ) + self.add_slide( + "

    Things to keep in mind


      \n" + "
    • You may need to adjust default settings
      " + "for your bot to remain undetected.

    • \n" + "
    • Once your bot enters a website,
      " + "it should continue to act accordingly.

    • \n" + "
    • Improvise if your bot makes any mistakes.

    • \n" + "
    • Your bot should look human to avoid detection.
    \n", + ) + self.add_slide( + "

    Ethical concerns


      \n" + "
    • Don't use bots for evil purposes.

    • \n" + "
    • Do use bots with honorable intentions.

    • \n" + "
    • Do use bots for automating tedious manual tasks.

    • \n" + "
    • Do take the time to train & configure your bots.
    \n", + ) + self.add_slide( + "

    πŸ”Ή Final remarks πŸ”Ή



      \n" + "
    • Not all bots are created equal.

    • \n" + "
    • SeleniumBase UC Mode lets bots appear human.

    • \n" + "
    • Visit SeleniumBase on GitHub for more info:\n" + "https://github.com/seleniumbase/SeleniumBase

    \n", + ) + self.add_slide( + "

    ❓ Questions? ❓

    " + "
    https://github.com/seleniumbase/SeleniumBase/discussions
    " + "

    πŸ“Œ Found a bug? 🐞

    " + "https://github.com/seleniumbase/SeleniumBase/issues

    " + "

    πŸ”° Perfection takes practice. Keep iterating! πŸ”°

    " + ) + self.add_slide( + "

    The End

    ", + image="https://seleniumbase.io/other/sb_github.png" + ) + self.begin_presentation(filename="uc_presentation.html") From 880c2eb2d54cfbbf25bae20ddc56389951acfca1 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:37:17 -0400 Subject: [PATCH 11/14] Refresh mkdocs dependencies --- mkdocs_build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 1172696e87f..af657f69677 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -15,7 +15,7 @@ ghp-import==2.1.0 watchdog==3.0.0 cairocffi==1.6.1 pathspec==0.11.2 -Babel==2.13.0 +Babel==2.13.1 paginate==0.5.6 pyquery==2.0.0 readtime==3.0.0 From d0eb1b399b0aff6d38eddd332d032ad8d92973cf Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:37:42 -0400 Subject: [PATCH 12/14] Update GitHub Actions --- .github/workflows/python-nightly-mac.yml | 4 ++-- .github/workflows/python-nightly-ubuntu.yml | 4 ++-- .github/workflows/python-nightly-windows.yml | 4 ++-- .github/workflows/python-package.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-nightly-mac.yml b/.github/workflows/python-nightly-mac.yml index 960f4bcfff6..3092184d17a 100644 --- a/.github/workflows/python-nightly-mac.yml +++ b/.github/workflows/python-nightly-mac.yml @@ -16,10 +16,10 @@ jobs: fail-fast: false max-parallel: 6 matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/python-nightly-ubuntu.yml b/.github/workflows/python-nightly-ubuntu.yml index 8c7faa72416..cb107cc24a6 100644 --- a/.github/workflows/python-nightly-ubuntu.yml +++ b/.github/workflows/python-nightly-ubuntu.yml @@ -16,10 +16,10 @@ jobs: fail-fast: false max-parallel: 6 matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/python-nightly-windows.yml b/.github/workflows/python-nightly-windows.yml index b369311906f..3755e1bf294 100644 --- a/.github/workflows/python-nightly-windows.yml +++ b/.github/workflows/python-nightly-windows.yml @@ -16,10 +16,10 @@ jobs: fail-fast: false max-parallel: 6 matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6bc81e6dbe5..cb105350f5b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -22,7 +22,7 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From 7229ee50c7b123942cdbee7a5fd30bdfd29bb139 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:38:39 -0400 Subject: [PATCH 13/14] Refresh Python dependencies --- requirements.txt | 8 ++++---- setup.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index ee4ebbc679c..f20793513d9 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -pip>=23.3 +pip>=23.3.1 packaging>=23.2 setuptools>=68.0.0;python_version<"3.8" setuptools>=68.2.2;python_version>="3.8" @@ -13,14 +13,14 @@ parse-type>=0.6.2 six==1.16.0 idna==3.4 chardet==5.2.0 -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 urllib3>=1.26.18,<2;python_version<"3.10" urllib3>=1.26.18,<2.1.0;python_version>="3.10" requests==2.31.0 pynose==1.4.8 sniffio==1.3.0 h11==0.14.0 -outcome==1.3.0 +outcome==1.3.0.post0 trio==0.22.2 trio-websocket==0.11.1 wsproto==1.2.0 @@ -34,7 +34,7 @@ iniconfig==2.0.0 pluggy==1.2.0;python_version<"3.8" pluggy==1.3.0;python_version>="3.8" py==1.11.0 -pytest==7.4.2 +pytest==7.4.3 pytest-html==2.0.1 pytest-metadata==3.0.0 pytest-ordering==0.6 diff --git a/setup.py b/setup.py index 2b37486d6bd..c1def20fd92 100755 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ ], python_requires=">=3.7", install_requires=[ - 'pip>=23.3', + 'pip>=23.3.1', 'packaging>=23.2', 'setuptools>=68.0.0;python_version<"3.8"', 'setuptools>=68.2.2;python_version>="3.8"', @@ -146,14 +146,14 @@ "six==1.16.0", "idna==3.4", 'chardet==5.2.0', - 'charset-normalizer==3.3.0', + 'charset-normalizer==3.3.1', 'urllib3>=1.26.18,<2;python_version<"3.10"', 'urllib3>=1.26.18,<2.1.0;python_version>="3.10"', 'requests==2.31.0', "pynose==1.4.8", 'sniffio==1.3.0', 'h11==0.14.0', - 'outcome==1.3.0', + 'outcome==1.3.0.post0', 'trio==0.22.2', 'trio-websocket==0.11.1', 'wsproto==1.2.0', @@ -167,7 +167,7 @@ 'pluggy==1.2.0;python_version<"3.8"', 'pluggy==1.3.0;python_version>="3.8"', "py==1.11.0", - 'pytest==7.4.2', + 'pytest==7.4.3', "pytest-html==2.0.1", # Newer ones had issues 'pytest-metadata==3.0.0', "pytest-ordering==0.6", @@ -229,7 +229,7 @@ "pdfminer": [ 'pdfminer.six==20221105', 'cryptography==39.0.2;python_version<"3.9"', - 'cryptography==41.0.4;python_version>="3.9"', + 'cryptography==41.0.5;python_version>="3.9"', 'cffi==1.15.1;python_version<"3.8"', 'cffi==1.16.0;python_version>="3.8"', "pycparser==2.21", From 945bf22c364aeaaa52f0ca4e5bb186e7eadcf145 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 27 Oct 2023 03:40:05 -0400 Subject: [PATCH 14/14] Version 4.20.7 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 76e969eedcb..127b54a3a78 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.20.6" +__version__ = "4.20.7"