diff --git a/examples/boilerplates/samples/google_test.py b/examples/boilerplates/samples/google_test.py index f9ffad8b471..ba520f0b52e 100644 --- a/examples/boilerplates/samples/google_test.py +++ b/examples/boilerplates/samples/google_test.py @@ -6,9 +6,13 @@ class GoogleTests(BaseCase): def test_google_dot_com(self): self.open("https://google.com/ncr") - self.sleep(0.4) + self.assert_title_contains("Google") + self.sleep(0.25) self.save_screenshot_to_logs() # ("./latest_logs" folder) - self.sleep(0.2) + self.sleep(0.15) + self.hide_elements('iframe') # Hide "Sign in" pop-up + self.sleep(0.15) + self.save_screenshot_to_logs() self.type(HomePage.search_box, "github.com") self.assert_element(HomePage.search_button) self.assert_element(HomePage.feeling_lucky_button) diff --git a/examples/boilerplates/samples/test_page_objects.py b/examples/boilerplates/samples/test_page_objects.py index a4d812b1117..7215144d7f8 100644 --- a/examples/boilerplates/samples/test_page_objects.py +++ b/examples/boilerplates/samples/test_page_objects.py @@ -6,11 +6,17 @@ class GooglePage: def go_to_google(self, sb): sb.open("https://google.com/ncr") - sb.sleep(0.1) - sb.hide_elements('iframe') # Hide "Sign in" pop-up - sb.sleep(0.2) + + def assert_google_title(self, sb): + sb.assert_title_contains("Google") + + def hide_sign_in_pop_up(self, sb): + sb.sleep(0.25) + sb.hide_elements('iframe') + sb.sleep(0.15) def do_search(self, sb, search_term): + sb.sleep(0.05) sb.click('[title="Search"]') sb.type('[title="Search"]', search_term + "\n") @@ -30,6 +36,8 @@ def test_page_objects(self): search_term = "SeleniumBase.io Docs" expected_text = "SeleniumBase" GooglePage().go_to_google(self) + GooglePage().assert_google_title(self) + GooglePage().hide_sign_in_pop_up(self) GooglePage().do_search(self, search_term) self.assert_text(expected_text, "#search") GooglePage().click_search_result(self, expected_text) diff --git a/examples/capabilities/ReadMe.md b/examples/capabilities/ReadMe.md index 5317652bb20..a50dacb552e 100644 --- a/examples/capabilities/ReadMe.md +++ b/examples/capabilities/ReadMe.md @@ -2,7 +2,7 @@

Using Desired Capabilities

-You can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server such as BrowserStack, Sauce Labs, or another. +You can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server such as BrowserStack, Sauce Labs, or another. Sample run commands may look like this when run from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder: (The browser is now specified in the capabilities file.) @@ -47,7 +47,7 @@ capabilities = {
Parsing desired capabilities:
diff --git a/examples/capabilities/sample_cap_file_SL.py b/examples/capabilities/sample_cap_file_SL.py index ad0b2bca232..c1f2fa2ff98 100644 --- a/examples/capabilities/sample_cap_file_SL.py +++ b/examples/capabilities/sample_cap_file_SL.py @@ -1,5 +1,5 @@ # Desired capabilities example file for Sauce Labs -# Generate from https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ +# Generate from https://saucelabs.com/products/platform-configurator capabilities = { "browserName": "chrome", "browserVersion": "latest", diff --git a/examples/raw_parameter_script.py b/examples/raw_parameter_script.py index 7da0bf5ba63..ee7db91f9b4 100644 --- a/examples/raw_parameter_script.py +++ b/examples/raw_parameter_script.py @@ -71,6 +71,7 @@ sb.no_sandbox = False sb.disable_js = False sb.disable_gpu = False + sb.log_cdp_events = False sb._multithreaded = False sb._reuse_session = False sb._crumbs = False diff --git a/examples/test_todomvc.py b/examples/test_todomvc.py index d8860e884b6..7f73e794015 100644 --- a/examples/test_todomvc.py +++ b/examples/test_todomvc.py @@ -10,6 +10,10 @@ def test_todomvc(self, framework): self.clear_local_storage() self.click('a[href="examples/%s"]' % framework) self.assert_element("section.todoapp") + self.assert_text("todos", "header h1") + self.wait_for_ready_state_complete() + title = self.get_title() + self.assert_in(framework, title.lower()) new_todo_input = "input.new-todo" todo_count_span = "span.todo-count" self.type(new_todo_input, "Learn Python\n") diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 92ae84c9740..2b4f3a270c1 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -452,7 +452,7 @@ pytest --settings-file=custom_settings.py

Running tests on a remote Selenium Grid:

-🌐 SeleniumBase lets you run tests on remote Selenium Grids such as [BrowserStack](https://www.browserstack.com/automate#)'s Selenium Grid, [Sauce Labs](https://saucelabs.com/products/open-source-frameworks/selenium)'s Selenium Grid, other Grids, and even your own Grid: +🌐 SeleniumBase lets you run tests on remote Selenium Grids such as [BrowserStack](https://www.browserstack.com/automate#)'s Selenium Grid, [Sauce Labs](https://saucelabs.com/products/platform-configurator)'s Selenium Grid, other Grids, and even your own Grid: 🌐 For setting browser desired capabilities while running Selenium remotely, see the ReadMe located here: https://github.com/seleniumbase/SeleniumBase/tree/master/examples/capabilities diff --git a/help_docs/desired_capabilities.md b/help_docs/desired_capabilities.md index 3f3f8dc1dea..768db3fd6d0 100644 --- a/help_docs/desired_capabilities.md +++ b/help_docs/desired_capabilities.md @@ -2,7 +2,7 @@ ## [](https://github.com/seleniumbase/SeleniumBase/) Using Desired Capabilities -You can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server such as BrowserStack or Sauce Labs. +You can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server such as BrowserStack or Sauce Labs. Sample run commands may look like this when run from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder: (The browser is now specified in the capabilities file.) @@ -47,7 +47,7 @@ capabilities = {
Parsing desired capabilities:
diff --git a/help_docs/how_it_works.md b/help_docs/how_it_works.md index 232a06857a2..36f44d29c76 100644 --- a/help_docs/how_it_works.md +++ b/help_docs/how_it_works.md @@ -4,17 +4,17 @@ -πŸ‘οΈπŸ”Ž At the core, SeleniumBase works by extending [pytest](https://docs.pytest.org/en/latest/) as a direct plugin. SeleniumBase automatically spins up web browsers for tests (using [Selenium WebDriver](https://www.selenium.dev/documentation/webdriver/)), and then gives those tests access to the SeleniumBase libraries through the [BaseCase class](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py). Tests are also given access to [SeleniumBase command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) and [SeleniumBase methods](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md), which provide additional functionality. +πŸ‘οΈπŸ”Ž The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) works by extending [pytest](https://docs.pytest.org/en/latest/) as a direct plugin. SeleniumBase automatically spins up web browsers for tests (using [Selenium WebDriver](https://www.selenium.dev/documentation/webdriver/)), and then gives those tests access to the SeleniumBase libraries through the [BaseCase class](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py). Tests are also given access to [SeleniumBase command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) and [SeleniumBase methods](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md), which provide additional functionality. πŸ‘οΈπŸ”Ž ``pytest`` uses a feature called test discovery to automatically find and run Python methods that start with ``test_`` when those methods are located in Python files that start with ``test_`` or end with ``_test.py``. -πŸ‘οΈπŸ”Ž The most common way of using **SeleniumBase** is by importing ``BaseCase``: +πŸ‘οΈπŸ”Ž The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) starts by importing ``BaseCase``: ```python from seleniumbase import BaseCase ``` -πŸ‘οΈπŸ”Ž This line activates ``pytest`` when a file is called directly with ``python``: +πŸ‘οΈπŸ”Ž This next line activates ``pytest`` when a file is called directly with ``python`` by accident: ```python BaseCase.main(__name__, __file__) @@ -29,12 +29,16 @@ class MyTestClass(BaseCase): πŸ‘οΈπŸ”Ž Test methods inside ``BaseCase`` classes become SeleniumBase tests: (These tests automatically launch a web browser before starting, and quit the web browser after ending. Default settings can be changed via command-line options.) ```python +class MyTestClass(BaseCase): def test_abc(self): + # ... ``` -πŸ‘οΈπŸ”Ž SeleniumBase APIs can be called from tests via ``self``: +πŸ‘οΈπŸ”Ž [SeleniumBase APIs](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md) can be called from tests via ``self``: ```python +class MyTestClass(BaseCase): + def test_abc(self): self.open("https://example.com") ``` @@ -44,22 +48,20 @@ class MyTestClass(BaseCase): from seleniumbase import BaseCase BaseCase.main(__name__, __file__) -class TestMFALogin(BaseCase): - def test_mfa_login(self): - self.open("https://seleniumbase.io/realworld/login") +class TestSimpleLogin(BaseCase): + def test_simple_login(self): + self.open("https://seleniumbase.io/simple/login") self.type("#username", "demo_user") self.type("#password", "secret_pass") - self.enter_mfa_code("#totpcode", "GAXG2MTEOR3DMMDG") # 6-digit - self.assert_text("Welcome!", "h1") - self.highlight("img#image1") # A fancier assert_element() call - self.click('a:contains("This Page")') # Use :contains() on any tag - self.save_screenshot_to_logs() # ("./latest_logs" folder for test) - self.click_link("Sign out") # Link must be "a" tag. Not "button". - self.assert_element('a:contains("Sign in")') - self.assert_exact_text("You have been signed out!", "#top_message") + self.click('a:contains("Sign in")') + self.assert_exact_text("Welcome!", "h1") + self.assert_element("img#image1") + self.highlight("#image1") + self.click_link("Sign out") + self.assert_text("signed out", "#top_message") ``` -(See the example, [test_mfa_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_mfa_login.py), for reference.) +(See the example, [test_simple_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py), for reference.) πŸ‘οΈπŸ”Ž Here are some examples of running tests with ``pytest``: @@ -71,7 +73,29 @@ pytest -k agent pytest offline_examples/ ``` -(See Syntax_Formats for more ways of structuring SeleniumBase tests.) +πŸ‘οΈπŸ”Ž Here's a [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) that uses the raw `driver`. Unlike the format mentioned earlier, it can be run with `python` instead of `pytest`. The `driver` includes original `driver` methods and new ones added by SeleniumBase: + +```python +from seleniumbase import Driver + +driver = Driver() +try: + driver.get("https://seleniumbase.io/simple/login") + driver.type("#username", "demo_user") + driver.type("#password", "secret_pass") + driver.click('a:contains("Sign in")') + driver.assert_exact_text("Welcome!", "h1") + driver.assert_element("img#image1") + driver.highlight("#image1") + driver.click_link("Sign out") + driver.assert_text("signed out", "#top_message") +finally: + driver.quit() +``` + +(See the example, [raw_login_driver.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_driver.py), for reference.) + +πŸ‘οΈπŸ”Ž Note that regular SeleniumBase formats (ones that use `BaseCase`, the `SB` context manager, or the `sb` `pytest` fixture) have more methods than the improved `driver` format. The regular formats also have more features. Some features, (such as the [SeleniumBase dashboard](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md)), require a `pytest` format. -------- diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 9b83d9ada33..06b1f7ce00e 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -282,6 +282,7 @@ self.get_new_driver( undetectable=None, uc_cdp_events=None, uc_subprocess=None, + log_cdp_events=None, no_sandbox=None, disable_gpu=None, headless2=None, diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index af657f69677..dd8d2b128c2 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -20,7 +20,7 @@ paginate==0.5.6 pyquery==2.0.0 readtime==3.0.0 mkdocs==1.5.3 -mkdocs-material==9.4.6 +mkdocs-material==9.4.7 mkdocs-exclude-search==0.6.5 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3 diff --git a/requirements.txt b/requirements.txt index f20793513d9..6d313534f18 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ wheel>=0.41.2 attrs>=23.1.0 certifi>=2023εΉ΄7月22ζ—₯ filelock>=3.12.2;python_version<"3.8" -filelock>=3.12.4;python_version>="3.8" +filelock>=3.13.0;python_version>="3.8" platformdirs>=3.11.0 parse>=1.19.1 parse-type>=0.6.2 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 127b54a3a78..84960854e03 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.20.7" +__version__ = "4.20.8" diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 263d61c0eea..e2830757a93 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -75,6 +75,7 @@ -D enable-sync (Enable "Chrome Sync".) -D uc | -D undetected (Use undetected-chromedriver to evade bot-detection) -D uc-cdp-events (Capture CDP events when running in "-D undetected" mode) +-D log-cdp ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"}) -D remote-debug (Sync to Chrome Remote Debugger chrome://inspect/#devices) -D dashboard (Enable the SeleniumBase Dashboard. Saved at: dashboard.html) -D dash-title=STRING (Set the title shown for the generated dashboard.) @@ -181,6 +182,7 @@ def get_configured_sb(context): sb.undetectable = False sb.uc_cdp_events = False sb.uc_subprocess = False + sb.log_cdp_events = False sb.no_sandbox = False sb.disable_gpu = False sb._multithreaded = False @@ -549,6 +551,10 @@ def get_configured_sb(context): sb.uc_subprocess = True sb.undetectable = True continue + # Handle: -D log-cdp-events / log_cdp_events / log-cdp + if low_key in ["log-cdp-events", "log_cdp_events", "log-cdp"]: + sb.log_cdp_events = True + continue # Handle: -D no-sandbox / no_sandbox if low_key in ["no-sandbox", "no_sandbox"]: sb.no_sandbox = True diff --git a/seleniumbase/config/proxy_list.py b/seleniumbase/config/proxy_list.py index cdc7d923329..6e2f1caf525 100644 --- a/seleniumbase/config/proxy_list.py +++ b/seleniumbase/config/proxy_list.py @@ -25,7 +25,7 @@ PROXY_LIST = { "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) + "example3": "socks5://142.44.212.57:49006", # (Example) "proxy1": None, "proxy2": None, "proxy3": None, diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 750b465a122..862405c600f 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -584,8 +584,8 @@ def _add_chrome_proxy_extension( zip_it=True, multi_proxy=False, ): - """Implementation of https://stackoverflow.com/a/35293284 for - https://stackoverflow.com/questions/12848327/ + """Implementation of https://stackoverflow.com/a/35293284/7058266 + for https://stackoverflow.com/q/12848327/7058266 (Run Selenium on a proxy server that requires authentication.)""" args = " ".join(sys.argv) bypass_list = proxy_bypass_list @@ -714,6 +714,7 @@ def _set_chrome_options( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -775,6 +776,11 @@ def _set_chrome_options( prefs["enable_do_not_track"] = True if external_pdf: prefs["plugins.always_open_pdf_externally"] = True + if proxy_string or proxy_pac_url: + # Implementation of https://stackoverflow.com/q/65705775/7058266 + prefs["webrtc.ip_handling_policy"] = "disable_non_proxied_udp" + prefs["webrtc.multiple_routes_enabled"] = False + prefs["webrtc.nonproxied_udp_enabled"] = False chrome_options.add_experimental_option("prefs", prefs) if enable_sync: chrome_options.add_experimental_option( @@ -787,6 +793,10 @@ def _set_chrome_options( "excludeSwitches", ["enable-automation", "enable-logging", "enable-blink-features"], ) + if log_cdp_events: + chrome_options.set_capability( + "goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"} + ) if mobile_emulator and not is_using_uc(undetectable, browser_name): emulator_settings = {} device_metrics = {} @@ -1258,6 +1268,7 @@ def get_driver( undetectable=False, uc_cdp_events=False, uc_subprocess=False, + log_cdp_events=False, no_sandbox=False, disable_gpu=False, headless2=False, @@ -1468,6 +1479,7 @@ def get_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -1521,6 +1533,7 @@ def get_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -1578,6 +1591,7 @@ def get_remote_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -1698,6 +1712,7 @@ def get_remote_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -1861,6 +1876,7 @@ def get_remote_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -1974,6 +1990,7 @@ def get_local_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, @@ -2382,6 +2399,10 @@ def get_local_driver( edge_options.add_experimental_option( "excludeSwitches", ["enable-automation", "enable-logging"] ) + if log_cdp_events: + edge_options.set_capability( + "ms:loggingPrefs", {"performance": "ALL", "browser": "ALL"} + ) if not enable_sync: edge_options.add_argument("--disable-sync") if ( @@ -2712,6 +2733,7 @@ def get_local_driver( undetectable, uc_cdp_events, uc_subprocess, + log_cdp_events, no_sandbox, disable_gpu, headless2, diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 02d937701ee..7965a695d26 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -395,7 +395,7 @@ def click( self.__shadow_click(selector, timeout) return if self.__needs_minimum_wait() or self.browser == "safari": - time.sleep(0.03) + time.sleep(0.04) element = page_actions.wait_for_element_visible( self.driver, selector, @@ -632,13 +632,13 @@ def click( except Exception: pass if self.__needs_minimum_wait() or self.browser == "safari": - time.sleep(0.03) + time.sleep(0.04) try: if self.driver.current_url != pre_action_url: self.__ad_block_as_needed() self.__disable_beforeunload_as_needed() if self.__needs_minimum_wait(): - time.sleep(0.03) + time.sleep(0.04) except Exception: try: self.wait_for_ready_state_complete() @@ -951,6 +951,10 @@ def update_text( raise if settings.WAIT_FOR_RSC_ON_PAGE_LOADS: self.wait_for_ready_state_complete() + if self.__needs_minimum_wait(): + time.sleep(0.03) + if self.undetectable: + time.sleep(0.025) except Exception: self.wait_for_ready_state_complete() time.sleep(0.14) @@ -975,9 +979,9 @@ def update_text( if settings.WAIT_FOR_RSC_ON_PAGE_LOADS: self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): - time.sleep(0.01) + time.sleep(0.03) if self.undetectable: - time.sleep(0.015) + time.sleep(0.025) if ( retry and element.get_attribute("value") != text @@ -3737,6 +3741,7 @@ def get_new_driver( undetectable=None, uc_cdp_events=None, uc_subprocess=None, + log_cdp_events=None, no_sandbox=None, disable_gpu=None, headless2=None, @@ -3793,6 +3798,7 @@ def get_new_driver( undetectable - the option to use an undetectable chromedriver uc_cdp_events - capture CDP events in "undetectable" mode (Chrome) uc_subprocess - use the undetectable chromedriver as a subprocess + log_cdp_events - capture {"performance": "ALL", "browser": "ALL"}) no_sandbox - the option to enable the "No-Sandbox" feature (Chrome) disable_gpu - the option to enable Chrome's "Disable GPU" feature headless2 - the option to use the newer headless mode (Chromium) @@ -3895,6 +3901,8 @@ def get_new_driver( uc_cdp_events = self.uc_cdp_events if uc_subprocess is None: uc_subprocess = self.uc_subprocess + if log_cdp_events is None: + log_cdp_events = self.log_cdp_events if no_sandbox is None: no_sandbox = self.no_sandbox if disable_gpu is None: @@ -3992,6 +4000,7 @@ def get_new_driver( undetectable=undetectable, uc_cdp_events=uc_cdp_events, uc_subprocess=uc_subprocess, + log_cdp_events=log_cdp_events, no_sandbox=no_sandbox, disable_gpu=disable_gpu, headless2=headless2, @@ -6730,11 +6739,13 @@ def choose_file( timeout = self.__get_new_timeout(timeout) selector, by = self.__recalculate_selector(selector, by) abs_path = os.path.abspath(file_path) + if self.__needs_minimum_wait(): + time.sleep(0.02) element = self.wait_for_element_present( selector, by=by, timeout=timeout ) if self.__needs_minimum_wait(): - time.sleep(0.08) # Force a minimum wait, even if skipping waits. + time.sleep(0.08) if self.is_element_visible(selector, by=by): self.__demo_mode_highlight_if_active(selector, by) if not self.demo_mode and not self.slow_mode: @@ -12946,7 +12957,7 @@ def __click_with_offset( self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): - time.sleep(0.14) # Force a minimum wait, even if skipping waits. + time.sleep(0.14) if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -14233,6 +14244,7 @@ def setUp(self, masterqa_mode=False): self.undetectable = sb_config.undetectable self.uc_cdp_events = sb_config.uc_cdp_events self.uc_subprocess = sb_config.uc_subprocess + self.log_cdp_events = sb_config.log_cdp_events self.no_sandbox = sb_config.no_sandbox self.disable_gpu = sb_config.disable_gpu self.headless2 = sb_config.headless2 @@ -14556,6 +14568,7 @@ def setUp(self, masterqa_mode=False): undetectable=self.undetectable, uc_cdp_events=self.uc_cdp_events, uc_subprocess=self.uc_subprocess, + log_cdp_events=self.log_cdp_events, no_sandbox=self.no_sandbox, disable_gpu=self.disable_gpu, headless2=self.headless2, diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index 30a5f38b5be..e83157d2121 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -111,7 +111,7 @@ def is_jquery_activated(driver): def wait_for_jquery_active(driver, timeout=None): if not timeout: - timeout = 22 + timeout = 2 else: timeout = int(timeout * 10.0) for x in range(timeout): @@ -778,7 +778,7 @@ def activate_messenger(driver): if not is_jquery_activated(driver): add_js_link(driver, jquery_js) - wait_for_jquery_active(driver, timeout=1) + wait_for_jquery_active(driver, timeout=1.1) add_css_link(driver, messenger_css) add_css_link(driver, msgr_theme_flat_css) add_css_link(driver, msgr_theme_future_css) @@ -894,11 +894,11 @@ def post_message(driver, message, msg_dur=None, style="info"): try: driver.execute_script(messenger_script) except Exception: - time.sleep(0.2) + time.sleep(0.17) activate_messenger(driver) - time.sleep(0.2) + time.sleep(0.17) set_messenger_theme(driver) - time.sleep(0.3) + time.sleep(0.27) driver.execute_script(messenger_script) diff --git a/seleniumbase/fixtures/page_utils.py b/seleniumbase/fixtures/page_utils.py index 0503564a361..57488185d31 100644 --- a/seleniumbase/fixtures/page_utils.py +++ b/seleniumbase/fixtures/page_utils.py @@ -252,11 +252,18 @@ def _get_unique_links(page_url, soup): link = prefix + link elif link.startswith("/"): link = full_base_url + link + elif link == "./": + link = page_url elif link.startswith("./"): f_b_url = full_base_url if len(simple_url.split("/"))> 1: f_b_url = full_base_url + "/" + simple_url.split("/")[1] link = f_b_url + link[1:] + elif link.startswith("../"): + if page_url.endswith("/"): + link = page_url + link + else: + link = page_url + "/" + link elif link.startswith("#"): link = full_base_url + link elif "//" not in link: diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 3745f24c550..1aa0f0f34d8 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -88,6 +88,7 @@ def Driver( undetectable=None, # Use undetected-chromedriver to evade bot-detection. uc_cdp_events=None, # Capture CDP events in undetected-chromedriver mode. uc_subprocess=None, # Use undetected-chromedriver as a subprocess. + log_cdp_events=None, # capture {"performance": "ALL", "browser": "ALL"}) no_sandbox=None, # (DEPRECATED) - "--no-sandbox" is always used now. disable_gpu=None, # (DEPRECATED) - GPU is disabled if not "swiftshader". incognito=None, # Enable Chromium's Incognito mode. @@ -120,6 +121,7 @@ def Driver( undetected=None, # Shortcut / Duplicate of "undetectable". uc_cdp=None, # Shortcut / Duplicate of "uc_cdp_events". uc_sub=None, # Shortcut / Duplicate of "uc_subprocess". + log_cdp=None, # Shortcut / Duplicate of "log_cdp_events". wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". ): @@ -352,6 +354,20 @@ def Driver( uc_cdp_events = True else: uc_cdp_events = False + if log_cdp_events is None and log_cdp is None: + if ( + "--log-cdp-events" in sys_argv + or "--log_cdp_events" in sys_argv + or "--log-cdp" in sys_argv + or "--log_cdp" in sys_argv + ): + log_cdp_events = True + else: + log_cdp_events = False + elif log_cdp_events or log_cdp: + log_cdp_events = True + else: + log_cdp_events = False if use_auto_ext is None: if "--use-auto-ext" in sys_argv: use_auto_ext = True @@ -458,6 +474,7 @@ def Driver( undetectable=undetectable, uc_cdp_events=uc_cdp_events, uc_subprocess=uc_subprocess, + log_cdp_events=log_cdp_events, no_sandbox=no_sandbox, disable_gpu=disable_gpu, headless2=headless2, diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index e56dbbebca4..430b0b459df 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -90,6 +90,7 @@ def pytest_addoption(parser): --enable-sync (Enable "Chrome Sync" on websites.) --uc | --undetected (Use undetected-chromedriver to evade bot-detection.) --uc-cdp-events (Capture CDP events when running in "--undetected" mode.) + --log-cdp ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"}) --remote-debug (Sync to Chrome Remote Debugger chrome://inspect/#devices) --ftrace | --final-trace (Debug Mode after each test. Don't use with CI!) --dashboard (Enable the SeleniumBase Dashboard. Saved at: dashboard.html) @@ -1012,6 +1013,17 @@ def pytest_addoption(parser): Using this enables the "Disable GPU" feature. (GPU is disabled by default if swiftshader off.)""", ) + parser.addoption( + "--log_cdp", + "--log-cdp", + "--log_cdp_events", + "--log-cdp-events", + action="store_true", + dest="log_cdp_events", + default=None, + help="""Capture CDP events. Then you can print them. + Eg. print(driver.get_log("performance"))""", + ) parser.addoption( "--remote_debug", "--remote-debug", @@ -1520,6 +1532,7 @@ def pytest_configure(config): sb_config.undetectable = True sb_config.no_sandbox = config.getoption("no_sandbox") sb_config.disable_gpu = config.getoption("disable_gpu") + sb_config.log_cdp_events = config.getoption("log_cdp_events") sb_config.remote_debug = config.getoption("remote_debug") sb_config.final_debug = config.getoption("final_debug") sb_config.dashboard = config.getoption("dashboard") diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 46577b3bde3..3128c7e768c 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -54,6 +54,7 @@ def SB( undetectable=None, # Use undetected-chromedriver to evade bot-detection. uc_cdp_events=None, # Capture CDP events in undetected-chromedriver mode. uc_subprocess=None, # Use undetected-chromedriver as a subprocess. + log_cdp_events=None, # capture {"performance": "ALL", "browser": "ALL"}) incognito=None, # Enable Chromium's Incognito mode. guest_mode=None, # Enable Chromium's Guest mode. dark_mode=None, # Enable Chromium's Dark mode. @@ -99,6 +100,7 @@ def SB( undetected=None, # Shortcut / Duplicate of "undetectable". uc_cdp=None, # Shortcut / Duplicate of "uc_cdp_events". uc_sub=None, # Shortcut / Duplicate of "uc_subprocess". + log_cdp=None, # Shortcut / Duplicate of "log_cdp_events". wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". sjw=None, # Shortcut / Duplicate of "skip_js_waits". @@ -470,6 +472,20 @@ def SB( uc_cdp_events = True else: uc_cdp_events = False + if log_cdp_events is None and log_cdp is None: + if ( + "--log-cdp-events" in sys_argv + or "--log_cdp_events" in sys_argv + or "--log-cdp" in sys_argv + or "--log_cdp" in sys_argv + ): + log_cdp_events = True + else: + log_cdp_events = False + elif log_cdp_events or log_cdp: + log_cdp_events = True + else: + log_cdp_events = False if use_auto_ext is None: if "--use-auto-ext" in sys_argv: use_auto_ext = True @@ -659,6 +675,7 @@ def SB( sb_config.undetectable = undetectable sb_config.uc_cdp_events = uc_cdp_events sb_config.uc_subprocess = uc_subprocess + sb_config.log_cdp_events = log_cdp_events sb_config.no_sandbox = None sb_config.disable_gpu = None sb_config.disable_js = disable_js @@ -761,6 +778,7 @@ def SB( sb.undetectable = sb_config.undetectable sb.uc_cdp_events = sb_config.uc_cdp_events sb.uc_subprocess = sb_config.uc_subprocess + sb.log_cdp_events = sb_config.log_cdp_events sb.no_sandbox = sb_config.no_sandbox sb.disable_gpu = sb_config.disable_gpu sb.disable_js = sb_config.disable_js diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index ac99818453c..f8c45536ae9 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -69,6 +69,7 @@ class SeleniumBrowser(Plugin): --enable-sync (Enable "Chrome Sync" on websites.) --uc | --undetected (Use undetected-chromedriver to evade bot-detection.) --uc-cdp-events (Capture CDP events when running in "--undetected" mode.) + --log-cdp ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"}) --remote-debug (Sync to Chrome Remote Debugger chrome://inspect/#devices) --enable-3d-apis (Enables WebGL and 3D APIs.) --swiftshader (Chrome "--use-gl=angle" / "--use-angle=swiftshader-webgl") @@ -730,7 +731,8 @@ def options(self, parser, env): action="store_true", dest="no_sandbox", default=False, - help="""Using this enables the "No Sandbox" feature. + help="""(DEPRECATED) - "--no-sandbox" is always used now. + Using this enables the "No Sandbox" feature. (This setting is now always enabled by default.)""", ) parser.addoption( @@ -739,8 +741,20 @@ def options(self, parser, env): action="store_true", dest="disable_gpu", default=False, - help="""Using this enables the "Disable GPU" feature. - (This setting is now always enabled by default.)""", + help="""(DEPRECATED) - GPU is disabled if no swiftshader. + Using this enables the "Disable GPU" feature. + (GPU is disabled by default if swiftshader off.)""", + ) + parser.addoption( + "--log_cdp", + "--log-cdp", + "--log_cdp_events", + "--log-cdp-events", + action="store_true", + dest="log_cdp_events", + default=None, + help="""Capture CDP events. Then you can print them. + Eg. print(driver.get_log("performance"))""", ) parser.addoption( "--remote_debug", @@ -1103,6 +1117,7 @@ def beforeTest(self, test): test.test.use_auto_ext = self.options.use_auto_ext test.test.undetectable = self.options.undetectable test.test.uc_cdp_events = self.options.uc_cdp_events + test.test.log_cdp_events = self.options.log_cdp_events if test.test.uc_cdp_events and not test.test.undetectable: test.test.undetectable = True test.test.uc_subprocess = self.options.uc_subprocess diff --git a/seleniumbase/utilities/selenium_grid/ReadMe.md b/seleniumbase/utilities/selenium_grid/ReadMe.md index 1a2f3c02bad..ff9430fb826 100644 --- a/seleniumbase/utilities/selenium_grid/ReadMe.md +++ b/seleniumbase/utilities/selenium_grid/ReadMe.md @@ -58,7 +58,7 @@ You can also run your tests on someone else's Selenium Grid to avoid managing yo pytest test_demo_site.py --server=USERNAME:KEY@hub.browserstack.com --port=80 ``` -* [Sauce Labs](https://saucelabs.com/products/open-source-frameworks/selenium) Selenium Grid: +* [Sauce Labs](https://saucelabs.com/products/platform-configurator) Selenium Grid: ```bash pytest test_demo_site.py --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https diff --git a/setup.py b/setup.py index c1def20fd92..47f5a057bb6 100755 --- a/setup.py +++ b/setup.py @@ -139,7 +139,7 @@ 'attrs>=23.1.0', "certifi>=2023εΉ΄7月22ζ—₯", 'filelock>=3.12.2;python_version<"3.8"', - 'filelock>=3.12.4;python_version>="3.8"', + 'filelock>=3.13.0;python_version>="3.8"', 'platformdirs>=3.11.0', 'parse>=1.19.1', 'parse-type>=0.6.2',

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /