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',