diff --git a/README.md b/README.md index 075a9dc32a9..6312b683ec9 100755 --- a/README.md +++ b/README.md @@ -657,6 +657,7 @@ pytest test_coffee_cart.py --trace --headless2 # (Use the new headless mode, which supports extensions.) --headed # (Run tests in headed/GUI mode on Linux OS, where not default.) --xvfb # (Run tests using the Xvfb virtual display server on Linux OS.) +--xvfb-metrics=STRING # (Set Xvfb display size on Linux: "Width,Height".) --locale=LOCALE_CODE # (Set the Language Locale Code for the web browser.) --interval=SECONDS # (The autoplay interval for presentations & tour steps) --start-page=URL # (The starting URL for the web browser when tests begin.) @@ -701,6 +702,7 @@ pytest test_coffee_cart.py --trace --rcs | --reuse-class-session # (Reuse session for tests in class.) --crumbs # (Delete all cookies between tests reusing a session.) --disable-beforeunload # (Disable the "beforeunload" event on Chrome.) +--window-position=X,Y # (Set the browser's starting window position.) --window-size=WIDTH,HEIGHT # (Set the browser's starting window size.) --maximize # (Start tests with the browser window maximized.) --screenshot # (Save a screenshot at the end of each test.) @@ -869,7 +871,7 @@ pytest test_suite.py --dashboard --html=report.html Dashboard Pytest HTML Report -If viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/). +If viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/). You can also use ``--junit-xml=report.xml`` to get an xml report instead. Jenkins can use this file to display better reporting for your tests. diff --git a/examples/custom_settings.py b/examples/custom_settings.py index 2eccc6bd8eb..a9479015b74 100644 --- a/examples/custom_settings.py +++ b/examples/custom_settings.py @@ -64,6 +64,10 @@ # If True and --proxy=IP_ADDRESS:PORT is invalid, then error immediately. RAISE_INVALID_PROXY_STRING_EXCEPTION = True +# Default browser coordinates when opening new windows for tests. +WINDOW_START_X = 20 +WINDOW_START_Y = 54 + # Default browser resolutions when opening new windows for tests. # (Headless resolutions take priority, and include all browsers.) # (Firefox starts maximized by default when running in GUI Mode.) diff --git a/examples/dialog_boxes/dialog_box_tour.py b/examples/dialog_boxes/dialog_box_tour.py index 2e061732cc7..e02f830d318 100644 --- a/examples/dialog_boxes/dialog_box_tour.py +++ b/examples/dialog_boxes/dialog_box_tour.py @@ -108,7 +108,9 @@ def test_dialog_boxes(self): self.highlight_type('input[aria-label="Search"]', text + "\n") else: self.open("https://en.wikipedia.org/wiki/Special:Search") - self.highlight_type('input[id*="search"]', text + "\n") + self.highlight_type('input[id*="search"]', text) + self.sleep(1) + self.click("#searchform button") self.wait_for_ready_state_complete() self.sleep(1) self.highlight("body") diff --git a/examples/example_logs/ReadMe.md b/examples/example_logs/ReadMe.md index fc77d59af70..11c130b55da 100644 --- a/examples/example_logs/ReadMe.md +++ b/examples/example_logs/ReadMe.md @@ -74,9 +74,9 @@ pytest test_suite.py --dashboard --html=report.html -------- -If viewing ``pytest-html`` reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356) for the HTML to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/security/configuring-content-security-policy/). That setting can be changed from ``Manage Jenkins`` > ``Script Console`` by running: +If viewing ``pytest-html`` reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the HTML to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/security/configuring-content-security-policy/). That setting can be changed from ``Manage Jenkins`` > ``Script Console`` by running: -``` +```js System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "") ``` diff --git a/examples/presenter/uc_presentation.py b/examples/presenter/uc_presentation.py index ae5ff4dc2f2..c52dc7ad328 100644 --- a/examples/presenter/uc_presentation.py +++ b/examples/presenter/uc_presentation.py @@ -1,5 +1,6 @@ import os import subprocess +from contextlib import suppress from seleniumbase import BaseCase from seleniumbase import SB BaseCase.main(__name__, __file__) @@ -8,6 +9,7 @@ class UCPresentationClass(BaseCase): def test_presentation(self): self.open("data:,") + self._output_file_saves = False self.create_presentation(theme="beige", transition="fade") self.add_slide( "

A deep dive into undetectable automation, with:

" @@ -235,7 +237,8 @@ def test_presentation(self): "from seleniumbase import Driver\n\n" "driver = Driver(uc=True)\n" "try:\n" - ' driver.get("https://nowsecure.nl/#relax")\n' + ' driver.get("https://gitlab.com/users/sign_in")' + '\n' " driver.sleep(4)\n" " # DO MORE STUFF\n" "finally:\n" @@ -263,7 +266,7 @@ def test_presentation(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True) as sb: sb.get("https://seleniumbase.io/simple/login") sb.type("#username", "demo_user") @@ -274,8 +277,6 @@ def test_presentation(self): 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( @@ -298,7 +299,7 @@ def test_presentation(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, demo=True) as sb: sb.get("https://seleniumbase.io/simple/login") sb.type("#username", "demo_user") @@ -309,8 +310,6 @@ def test_presentation(self): 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( @@ -339,29 +338,22 @@ def test_presentation(self): code=( "from seleniumbase import SB\n\n" "with SB(uc=True) as sb:\n" - ' sb.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.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' + ' url = "https://gitlab.com/users/sign_in"\n' + " sb.uc_open_with_reconnect(url, 4)\n\n" + " ...\n" ), ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True) as sb: - sb.uc_open_with_tab("https://nowsecure.nl/#relax") - sb.sleep(1) - if not sb.is_text_visible("OH YEAH, you passed", "h1"): - sb.uc_open_with_tab("https://nowsecure.nl/#relax") - sb.sleep(1) - sb.activate_demo_mode() - sb.assert_text("OH YEAH, you passed!", "h1", timeout=3) - except Exception: - pass + url = "https://gitlab.com/users/sign_in" + sb.uc_open_with_reconnect(url, 4) + sb.assert_text("Username", '[for="user_login"]', timeout=3) + sb.assert_element('[for="user_login"]') + sb.highlight('button:contains("Sign in")') + sb.highlight('h1:contains("GitLab.com")') + sb.post_message("SeleniumBase wasn't detected", duration=4) self.create_presentation(theme="serif", transition="fade") self.add_slide( @@ -495,11 +487,12 @@ def test_presentation(self): code=( "# Example:\n" "driver.uc_open_with_reconnect(\n" - ' "https://nowsecure.nl/#relax", reconnect_time=6\n)' + ' "https://steamdb.info/login/", reconnect_time=6\n)' + "" "\n\n" "# Short form example:\n" "driver.uc_open_with_reconnect(" - '"https://nowsecure.nl/#relax", 6)\n' + '"https://steamdb.info/login/", 6)\n' ), ) self.add_slide( diff --git a/examples/presenter/uc_presentation_3.py b/examples/presenter/uc_presentation_3.py index a3da212dcd4..1b66fef2090 100644 --- a/examples/presenter/uc_presentation_3.py +++ b/examples/presenter/uc_presentation_3.py @@ -1,4 +1,5 @@ import sys +from contextlib import suppress from seleniumbase import BaseCase from seleniumbase import SB BaseCase.main(__name__, __file__) @@ -54,7 +55,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True) as sb: url = "https://gitlab.com/users/sign_in" sb.uc_open_with_reconnect(url, 4) @@ -63,8 +64,6 @@ def test_presentation_3(self): sb.highlight('button:contains("Sign in")') sb.highlight('h1:contains("GitLab.com")') sb.post_message("SeleniumBase wasn't detected", duration=4) - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -151,7 +150,7 @@ def test_presentation_3(self): if "linux" in sys.platform or "win32" in sys.platform: agent = None # Use the default UserAgent - try: + with suppress(Exception): with SB(uc=True, test=True, agent=agent) as sb: url = "https://gitlab.com/users/sign_in" sb.uc_open_with_reconnect(url, 4) @@ -159,8 +158,6 @@ def test_presentation_3(self): sb.assert_element('label[for="user_login"]') sb.set_messenger_theme(location="bottom_center") sb.post_message("SeleniumBase wasn't detected!") - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -201,7 +198,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, test=True, agent=agent) as sb: url = "https://gitlab.com/users/sign_in" sb.uc_open_with_reconnect(url, 4) @@ -209,8 +206,6 @@ def test_presentation_3(self): sb.assert_element('label[for="user_login"]') sb.set_messenger_theme(location="bottom_center") sb.post_message("SeleniumBase wasn't detected!") - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -276,7 +271,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, incognito=True, locale_code="en") as sb: url = "https://ahrefs.com/website-authority-checker" input_field = 'input[placeholder="Enter domain"]' @@ -291,8 +286,6 @@ def test_presentation_3(self): sb.highlight('a:contains("Top 100 backlinks")') sb.set_messenger_theme(location="bottom_center") sb.post_message("SeleniumBase wasn't detected!") - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -312,7 +305,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, test=True, disable_csp=True) as sb: url = "https://steamdb.info/" sb.uc_open_with_reconnect(url, 3) @@ -324,8 +317,6 @@ def test_presentation_3(self): sb.highlight('button:contains("Sign in")', scroll=False) sb.set_messenger_theme(location="top_center") sb.post_message("SeleniumBase wasn't detected", duration=4) - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -405,7 +396,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/apps/recaptcha" sb.uc_open_with_reconnect(url) @@ -413,8 +404,6 @@ def test_presentation_3(self): sb.assert_element("img#captcha-success", timeout=3) sb.set_messenger_theme(location="top_left") sb.post_message("SeleniumBase wasn't detected") - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -673,7 +662,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(test=True) as sb: url = "https://seleniumbase.io/hobbit/login" sb.open(url) @@ -682,8 +671,6 @@ def test_presentation_3(self): sb.click("img") sb.highlight("h1") sb.sleep(3) # Gandalf: "You Shall Not Pass!" - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( @@ -692,7 +679,7 @@ def test_presentation_3(self): ) self.begin_presentation(filename="uc_presentation.html") - try: + with suppress(Exception): with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/hobbit/login" sb.uc_open_with_disconnect(url, 2.2) @@ -703,8 +690,6 @@ def test_presentation_3(self): sb.post_message("SeleniumBase wasn't detected!") sb.click("img") sb.sleep(5.888) # Cool animation happening now! - except Exception: - pass self.create_presentation(theme="serif", transition="none") self.add_slide( diff --git a/examples/test_hack_search.py b/examples/test_hack_search.py index 7ddb4691b3a..80090729a57 100644 --- a/examples/test_hack_search.py +++ b/examples/test_hack_search.py @@ -25,8 +25,9 @@ def test_hack_search(self): self.highlight_click('[href*="github.com/seleniumbase/SeleniumBase"]') self.highlight_click('[href="/seleniumbase/SeleniumBase"]') self.assert_text("SeleniumBase", "strong a") + self.highlight("strong a") self.js_click('a[title="examples"]') - self.highlight('td[class*="large"] a[title="test_hack_search.py"]') - self.click('td[class*="large"] a[title="test_hack_search.py"]') + self.highlight('#repo-content-turbo-frame') + self.js_click('a[title="test_hack_search.py"]') self.assert_text("test_hack_search.py", "#file-name-id-wide") self.highlight("#file-name-id-wide") diff --git a/examples/test_parse_soup.py b/examples/test_parse_soup.py index ee5cfa28a6a..4d8205cf1ac 100644 --- a/examples/test_parse_soup.py +++ b/examples/test_parse_soup.py @@ -14,6 +14,9 @@ def click_menu_item(self, text): self.click("#%s" % the_id) def test_beautiful_soup_parsing(self): + if self.headless: + self.open_if_not_url("about:blank") + self.skip("Skip this test in headless mode!") self.open("https://seleniumbase.io/tinymce/") self.wait_for_element("div.mce-container-body") self.click_menu_item("File") diff --git a/examples/test_shadow_dom.py b/examples/test_shadow_dom.py index cdc51167d7a..2196155be1f 100644 --- a/examples/test_shadow_dom.py +++ b/examples/test_shadow_dom.py @@ -55,7 +55,7 @@ def test_shadow_dom(self): ) remove_button = ( "downloads-manager::shadow #downloadsList" - " downloads-item::shadow #remove-old" + " downloads-item::shadow #quick-remove" ) no_downloads_area = "downloads-manager::shadow #no-downloads" diff --git a/examples/test_tinymce.py b/examples/test_tinymce.py index 5dfef56319c..9c81cd69cfe 100644 --- a/examples/test_tinymce.py +++ b/examples/test_tinymce.py @@ -4,6 +4,9 @@ class TinyMceTests(BaseCase): def test_tinymce(self): + if self.headless: + self.open_if_not_url("about:blank") + self.skip("Skip this test in headless mode!") self.open("https://seleniumbase.io/tinymce/") self.wait_for_element("div.mce-container-body") self.click('span:contains("File")') diff --git a/examples/youtube_search_test.py b/examples/youtube_search_test.py index 55448a61e35..96f119340f1 100644 --- a/examples/youtube_search_test.py +++ b/examples/youtube_search_test.py @@ -12,20 +12,19 @@ def test_youtube_autocomplete_results(self): self.open("https://www.youtube.com/c/MichaelMintz") search_term = "seleniumbase" search_selector = "input#search" - result_selector = 'li[role="presentation"]' + results_selector = '[role="listbox"]' self.click_if_visible('button[aria-label="Close"]') self.double_click(search_selector) self.sleep(0.15) self.type(search_selector, search_term) self.sleep(0.15) # First verify that an autocomplete result exists - self.assert_element(result_selector) - top_result = self.get_text(result_selector) + self.assert_element(results_selector) + top_results = self.get_text(results_selector) # Now verify that the autocomplete result is good self.assert_true( - search_term in top_result, - 'Expected text "%s" not found in top result! ' - 'Actual text was "%s"!' % (search_term, top_result), + search_term in top_results, + 'Expected text "%s" not found in top results! ' + 'Actual text was "%s"!' % (search_term, top_results), ) - self.click(result_selector) self.sleep(1) diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 64ccda7a5ae..8566d968346 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -148,6 +148,7 @@ pytest my_first_test.py --settings-file=custom_settings.py --headless2 # (Use the new headless mode, which supports extensions.) --headed # (Run tests in headed/GUI mode on Linux OS, where not default.) --xvfb # (Run tests using the Xvfb virtual display server on Linux OS.) +--xvfb-metrics=STRING # (Set Xvfb display size on Linux: "Width,Height".) --locale=LOCALE_CODE # (Set the Language Locale Code for the web browser.) --interval=SECONDS # (The autoplay interval for presentations & tour steps) --start-page=URL # (The starting URL for the web browser when tests begin.) @@ -192,6 +193,7 @@ pytest my_first_test.py --settings-file=custom_settings.py --rcs | --reuse-class-session # (Reuse session for tests in class.) --crumbs # (Delete all cookies between tests reusing a session.) --disable-beforeunload # (Disable the "beforeunload" event on Chrome.) +--window-position=X,Y # (Set the browser's starting window position.) --window-size=WIDTH,HEIGHT # (Set the browser's starting window size.) --maximize # (Start tests with the browser window maximized.) --screenshot # (Save a screenshot at the end of each test.) @@ -380,7 +382,7 @@ pytest test_suite.py --dashboard --html=report.html Dashboard Pytest HTML Report -If viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/). +If viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/). You can also use ``--junit-xml=report.xml`` to get an xml report instead. Jenkins can use this file to display better reporting for your tests. diff --git a/help_docs/webdriver_installation.md b/help_docs/webdriver_installation.md index 6365fe7f098..f5b8dd16c2b 100644 --- a/help_docs/webdriver_installation.md +++ b/help_docs/webdriver_installation.md @@ -2,7 +2,7 @@ ## [](https://github.com/seleniumbase/SeleniumBase/) Installing webdrivers -To run web automation, you'll need webdrivers for each browser you plan on using. With SeleniumBase, drivers are downloaded automatically as needed into the SeleniumBase ``drivers`` folder. +To run web automation, you need webdrivers for each browser you plan on using. With SeleniumBase, drivers are downloaded automatically (as needed) into the SeleniumBase `drivers/` folder. You can also download drivers manually with these commands: @@ -12,7 +12,7 @@ seleniumbase get geckodriver seleniumbase get edgedriver ``` -After running the commands above, web drivers will get downloaded into the ``seleniumbase/drivers/`` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.) +After running the commands above, web drivers will get downloaded into the `seleniumbase/drivers/` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.) If the necessary driver is not found in this location while running tests, SeleniumBase will instead look for the driver on the System PATH. If the necessary driver is not on the System PATH either, SeleniumBase will automatically attempt to download the required driver. diff --git a/integrations/node_js/ReadMe.md b/integrations/node_js/ReadMe.md index 20558e59ffa..ec59492a1a7 100644 --- a/integrations/node_js/ReadMe.md +++ b/integrations/node_js/ReadMe.md @@ -19,13 +19,13 @@ You can create a customized web app for running SeleniumBase tests by using Node npm install -g npm@latest ``` -#### 3. Install the Example Test Runner for SeleniumBase from the [integrations/node_js](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js) folder (``npm ci`` has a speed improvement over ``npm install`` because it uses the ``npm-shrinkwrap.json`` file that's generated via ``npm shrinkwrap``.) +#### 3. Install the example Test Runner for SeleniumBase from [integrations/node_js](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js). (If dependencies were already installed, you can use `npm ci` for a speed improvement over `npm i` / `npm install` because `npm ci` uses `npm-shrinkwrap.json`, which is generated via ``npm shrinkwrap``.) ```bash -npm ci +npm install ``` -(You should see a ``node_modules`` folder appear in your ``node_js`` folder.) +(You should see a `node_modules` folder appear in your `node_js` folder.) #### 4. Run the NodeJS server for your SeleniumBase Test Runner web app diff --git a/integrations/node_js/index.html b/integrations/node_js/index.html index 50f37027260..dc6c64ebbad 100644 --- a/integrations/node_js/index.html +++ b/integrations/node_js/index.html @@ -1,11 +1,17 @@ - + diff --git a/integrations/node_js/npm-shrinkwrap.json b/integrations/node_js/npm-shrinkwrap.json index 7507126ad50..2fc839d54a8 100644 --- a/integrations/node_js/npm-shrinkwrap.json +++ b/integrations/node_js/npm-shrinkwrap.json @@ -8,7 +8,7 @@ "name": "app", "version": "0.0.0", "dependencies": { - "express": "~4.19.2" + "express": "~4.21.0" } }, "node_modules/accepts": { @@ -29,9 +29,9 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -41,7 +41,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -156,9 +156,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -196,36 +196,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -237,12 +237,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -398,9 +398,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -484,9 +487,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -501,11 +504,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -561,9 +564,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -583,20 +586,28 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" diff --git a/integrations/node_js/package.json b/integrations/node_js/package.json index 4873a0f1362..9a00008425b 100644 --- a/integrations/node_js/package.json +++ b/integrations/node_js/package.json @@ -6,6 +6,6 @@ "start": "node ./bin/www" }, "dependencies": { - "express": "~4.19.2" + "express": "~4.21.0" } } \ No newline at end of file diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index eb8f5c71152..bb862790a7b 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -2,8 +2,8 @@ # Minimum Python version: 3.8 (for generating docs only) regex>=2024.9.11 -pymdown-extensions>=10.9 -pipdeptree>=2.23.3 +pymdown-extensions>=10.10.1 +pipdeptree>=2.23.4 python-dateutil>=2.8.2 Markdown==3.7 markdown2==2.5.0 @@ -20,7 +20,7 @@ lxml==5.3.0 pyquery==2.0.1 readtime==3.0.0 mkdocs==1.6.1 -mkdocs-material==9.5.34 +mkdocs-material==9.5.36 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3.1 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..2697d39bd76 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[build-system] +requires = ["setuptools>=70.2.0", "wheel>=0.44.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "seleniumbase" +readme = "README.md" +dynamic = [ + "version", + "license", + "authors", + "scripts", + "keywords", + "classifiers", + "description", + "entry-points", + "dependencies", + "requires-python", + "optional-dependencies", +] + +[project.urls] +"Homepage" = "https://github.com/seleniumbase/SeleniumBase" +"Changelog" = "https://github.com/seleniumbase/SeleniumBase/releases" +"Download" = "https://pypi.org/project/seleniumbase/#files" +"Blog" = "https://seleniumbase.com/" +"Discord" = "https://discord.gg/EdhQTn3EyE" +"PyPI" = "https://pypi.org/project/seleniumbase/" +"Source" = "https://github.com/seleniumbase/SeleniumBase" +"Repository" = "https://github.com/seleniumbase/SeleniumBase" +"Documentation" = "https://seleniumbase.io/" + +[tool.setuptools] +packages = [ + "seleniumbase", + "sbase", + "seleniumbase.behave", + "seleniumbase.common", + "seleniumbase.config", + "seleniumbase.console_scripts", + "seleniumbase.core", + "seleniumbase.drivers", + "seleniumbase.extensions", + "seleniumbase.fixtures", + "seleniumbase.js_code", + "seleniumbase.masterqa", + "seleniumbase.plugins", + "seleniumbase.resources", + "seleniumbase.translate", + "seleniumbase.undetected", + "seleniumbase.utilities", + "seleniumbase.utilities.selenium_grid", + "seleniumbase.utilities.selenium_ide", +] + +[tool.pytest.ini_options] +addopts = ["--capture=no", "-p no:cacheprovider"] +norecursedirs = [".*", "build", "dist", "recordings", "temp", "assets"] +filterwarnings = [ + "ignore::pytest.PytestWarning", + "ignore:.*U.*mode is deprecated:DeprecationWarning", +] +junit_family = ["legacy"] +python_files = ["test_*.py", "*_test.py", "*_tests.py", "*_suite.py"] +python_classes = ["Test*", "*Test*", "*Test", "*Tests", "*Suite"] +python_functions = ["test_*"] +markers = [ + "marker1", "marker2", "marker3", "marker_test_suite", + "local", "remote", "offline", "expected_failure", + "qa", "ci", "e2e", "smoke", "ready", "master", "deploy", + "develop", "staging", "production", "release", "active", +] diff --git a/requirements.txt b/requirements.txt index 86b35e3b534..d4040dab303 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,87 +1,67 @@ -pip>=24.0;python_version<"3.8" -pip>=24.1.2;python_version>="3.8" -packaging>=24.0;python_version<"3.8" -packaging>=24.1;python_version>="3.8" -setuptools>=68.0.0;python_version<"3.8" -setuptools~=70.2;python_version>="3.8" and python_version<"3.10" +pip>=24.1.2 +packaging>=24.1 +setuptools~=70.2;python_version<"3.10" setuptools>=70.2.0;python_version>="3.10" -wheel>=0.42.0;python_version<"3.8" -wheel>=0.44.0;python_version>="3.8" +wheel>=0.44.0 attrs>=24.2.0 certifi>=2024.8.30 exceptiongroup>=1.2.2 -filelock>=3.12.2;python_version<"3.8" -filelock>=3.16.0;python_version>="3.8" -platformdirs>=4.0.0;python_version<"3.8" -platformdirs>=4.3.2;python_version>="3.8" -typing-extensions>=4.12.2;python_version>="3.8" +filelock>=3.16.1 +fasteners>=0.19 +pynose>=1.5.3 +platformdirs>=4.3.6 +typing-extensions>=4.12.2 +sbvirtualdisplay>=1.3.0 +six>=1.16.0 parse>=1.20.2 parse-type>=0.6.3 -pyyaml==6.0.1;python_version<"3.8" -pyyaml>=6.0.2;python_version>="3.8" -six==1.16.0 -idna==3.8 +colorama>=0.4.6 +pyyaml>=6.0.2 +pygments>=2.18.0 +pyreadline3>=3.5.3;platform_system=="Windows" +tabcompleter>=1.3.3 +pdbp>=1.5.4 +idna==3.10 chardet==5.2.0 charset-normalizer==3.3.2 urllib3>=1.26.20,<2;python_version<"3.10" urllib3>=1.26.20,<2.3.0;python_version>="3.10" requests==2.31.0 -pynose==1.5.2 sniffio==1.3.1 h11==0.14.0 outcome==1.3.0.post0 -trio==0.22.2;python_version<"3.8" -trio==0.26.2;python_version>="3.8" +trio==0.26.2 trio-websocket==0.11.1 wsproto==1.2.0 -websocket-client==1.8.0;python_version>="3.8" -selenium==4.11.2;python_version<"3.8" -selenium==4.24.0;python_version>="3.8" +websocket-client==1.8.0 +selenium==4.25.0 cssselect==1.2.0 sortedcontainers==2.4.0 -fasteners==0.19 -execnet==2.0.2;python_version<"3.8" -execnet==2.1.1;python_version>="3.8" +execnet==2.1.1 iniconfig==2.0.0 -pluggy==1.2.0;python_version<"3.8" -pluggy==1.5.0;python_version>="3.8" +pluggy==1.5.0 py==1.11.0 -pytest==7.4.4;python_version<"3.8" -pytest==8.3.3;python_version>="3.8" +pytest==8.3.3 pytest-html==2.0.1 -pytest-metadata==3.0.0;python_version<"3.8" -pytest-metadata==3.1.1;python_version>="3.8" +pytest-metadata==3.1.1 pytest-ordering==0.6 -pytest-rerunfailures==13.0;python_version<"3.8" -pytest-rerunfailures==14.0;python_version>="3.8" -pytest-xdist==3.5.0;python_version<"3.8" -pytest-xdist==3.6.1;python_version>="3.8" +pytest-rerunfailures==14.0 +pytest-xdist==3.6.1 parameterized==0.9.0 -sbvirtualdisplay==1.3.0 behave==1.2.6 -soupsieve==2.4.1;python_version<"3.8" -soupsieve==2.6;python_version>="3.8" +soupsieve==2.6 beautifulsoup4==4.12.3 -pygments==2.17.2;python_version<"3.8" -pygments==2.18.0;python_version>="3.8" -pyreadline3==3.4.1;platform_system=="Windows" -tabcompleter==1.3.3 -pdbp==1.5.4 -colorama==0.4.6 pyotp==2.9.0 python-xlib==0.33;platform_system=="Linux" -markdown-it-py==2.2.0;python_version<"3.8" -markdown-it-py==3.0.0;python_version>="3.8" +markdown-it-py==3.0.0 mdurl==0.1.2 rich==13.8.1 # --- Testing Requirements --- # # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) -coverage==7.2.7;python_version<"3.8" -coverage>=7.6.1;python_version>="3.8" -pytest-cov==4.1.0;python_version<"3.8" -pytest-cov>=5.0.0;python_version>="3.8" +coverage>=7.6.1 +pytest-cov>=5.0.0 flake8==5.0.4;python_version<"3.9" flake8==7.1.1;python_version>="3.9" mccabe==0.7.0 diff --git a/sbase/__init__.py b/sbase/__init__.py index b45beac8e68..ff1a31a52a3 100644 --- a/sbase/__init__.py +++ b/sbase/__init__.py @@ -11,3 +11,4 @@ from seleniumbase import page_actions # noqa from seleniumbase import page_utils # noqa from seleniumbase import SB # noqa +from seleniumbase import translate # noqa diff --git a/seleniumbase/__init__.py b/seleniumbase/__init__.py index e84a7d3628c..0f536be75ab 100644 --- a/seleniumbase/__init__.py +++ b/seleniumbase/__init__.py @@ -10,7 +10,7 @@ from seleniumbase.__version__ import __version__ from seleniumbase.common import decorators # noqa from seleniumbase.common import encryption # noqa -from seleniumbase.core import colored_traceback +from seleniumbase.core import colored_traceback # noqa from seleniumbase.core.browser_launcher import get_driver # noqa from seleniumbase.fixtures import js_utils # noqa from seleniumbase.fixtures import page_actions # noqa @@ -36,8 +36,7 @@ pdb.DefaultConfig.sticky_by_default = True colored_traceback.add_hook() os.environ["SE_AVOID_STATS"] = "true" # Disable Selenium Manager stats -if sys.version_info >= (3, 7): - webdriver.TouchActions = None # Lifeline for past selenium-wire versions +webdriver.TouchActions = None # Lifeline for past selenium-wire versions if sys.version_info >= (3, 10): collections.Callable = collections.abc.Callable # Lifeline for nosetests del collections # Undo "import collections" / Simplify "dir(seleniumbase)" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index ba052211991..429f6b81506 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.30.8" +__version__ = "4.31.0" diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 901bf76cbc5..17609334fcc 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -48,6 +48,7 @@ -D headless2 (Use the new headless mode, which supports extensions.) -D headed (Run tests in headed/GUI mode on Linux OS, where not default.) -D xvfb (Run tests using the Xvfb virtual display server on Linux OS.) +-D xvfb-metrics=STRING (Set Xvfb display size on Linux: "Width,Height".) -D locale=LOCALE_CODE (Set the Language Locale Code for the web browser.) -D pdb (Activate Post Mortem Debug Mode if a test fails.) -D interval=SECONDS (The autoplay interval for presentations & tour steps) @@ -90,6 +91,7 @@ -D rcs | -D reuse-class-session (Reuse session for tests in class/feature) -D crumbs (Delete all cookies between tests reusing a session.) -D disable-beforeunload (Disable the "beforeunload" event on Chrome.) +-D window-position=X,Y (Set the browser's starting window position.) -D window-size=WIDTH,HEIGHT (Set the browser's starting window size.) -D maximize (Start tests with the browser window maximized.) -D screenshot (Save a screenshot at the end of each test.) @@ -104,6 +106,7 @@ import os import re import sys +from contextlib import suppress from seleniumbase import config as sb_config from seleniumbase.config import settings from seleniumbase.core import download_helper @@ -145,6 +148,7 @@ def get_configured_sb(context): sb.headless_active = False sb.headed = False sb.xvfb = False + sb.xvfb_metrics = None sb.start_page = None sb.locale_code = None sb.pdb_option = False @@ -193,6 +197,7 @@ def get_configured_sb(context): sb._disable_beforeunload = False sb.visual_baseline = False sb.use_wire = False + sb.window_position = None sb.window_size = None sb.maximize_option = False sb.is_context_manager = False @@ -302,6 +307,13 @@ def get_configured_sb(context): if low_key == "xvfb": sb.xvfb = True continue + # Handle: -D xvfb-metrics=STR / xvfb_metrics=STR + if low_key in ["xvfb-metrics", "xvfb_metrics"]: + xvfb_metrics = userdata[key] + if xvfb_metrics == "true": + xvfb_metrics = sb.xvfb_metrics # revert to default + sb.xvfb_metrics = xvfb_metrics + continue # Handle: -D start-page=URL / start_page=URL / url=URL if low_key in ["start-page", "start_page", "url"]: start_page = userdata[key] @@ -601,6 +613,13 @@ def get_configured_sb(context): if low_key == "wire": sb.use_wire = True continue + # Handle: -D window-position=X,Y / window_position=X,Y + if low_key in ["window-position", "window_position"]: + window_position = userdata[key] + if window_position == "true": + window_position = sb.window_position # revert to default + sb.window_position = window_position + continue # Handle: -D window-size=Width,Height / window_size=Width,Height if low_key in ["window-size", "window_size"]: window_size = userdata[key] @@ -904,6 +923,29 @@ def get_configured_sb(context): else: sb.enable_ws = False sb.disable_ws = True + if sb.window_position: + window_position = sb.window_position + if window_position.count(",") != 1: + message = ( + '\n\n window_position expects an "x,y" string!' + '\n (Your input was: "%s")\n' % window_position + ) + raise Exception(message) + window_position = window_position.replace(" ", "") + win_x = None + win_y = None + try: + win_x = int(window_position.split(",")[0]) + win_y = int(window_position.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "x,y"!' + '\n (window_position input was: "%s")\n' + % window_position + ) + raise Exception(message) + settings.WINDOW_START_X = win_x + settings.WINDOW_START_Y = win_y if sb.window_size: window_size = sb.window_size if window_size.count(",") != 1: @@ -938,9 +980,11 @@ def get_configured_sb(context): sb_config.is_pytest = False sb_config.is_nosetest = False sb_config.is_context_manager = False + sb_config.window_position = sb.window_position sb_config.window_size = sb.window_size sb_config.maximize_option = sb.maximize_option sb_config.xvfb = sb.xvfb + sb_config.xvfb_metrics = sb.xvfb_metrics sb_config.reuse_class_session = sb._reuse_class_session sb_config.save_screenshot = sb.save_screenshot_after_test sb_config.no_screenshot = sb.no_screenshot_after_test @@ -1162,12 +1206,10 @@ def behave_dashboard_prepare(): sb_config.item_count_untested = sb_config.item_count dash_path = os.path.join(os.getcwd(), "dashboard.html") star_len = len("Dashboard: ") + len(dash_path) - try: + with suppress(Exception): terminal_size = os.get_terminal_size().columns if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size - except Exception: - pass stars = "*" * star_len c1 = "" cr = "" @@ -1263,7 +1305,7 @@ def _perform_behave_unconfigure_(): def do_final_driver_cleanup_as_needed(): - try: + with suppress(Exception): if hasattr(sb_config, "last_driver") and sb_config.last_driver: if ( not is_windows @@ -1271,8 +1313,6 @@ def do_final_driver_cleanup_as_needed(): or sb_config.last_driver.service.process ): sb_config.last_driver.quit() - except Exception: - pass def _perform_behave_terminal_summary_(): @@ -1281,12 +1321,10 @@ def _perform_behave_terminal_summary_(): ) dash_path = os.path.join(os.getcwd(), "dashboard.html") equals_len = len("Dashboard: ") + len(dash_path) - try: + with suppress(Exception): terminal_size = os.get_terminal_size().columns if terminal_size > 30 and equals_len > terminal_size: equals_len = terminal_size - except Exception: - pass equals = "=" * (equals_len + 2) c2 = "" cr = "" diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index 0b2822100f3..c0980f01f02 100644 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -110,6 +110,10 @@ # (This applies when using --proxy=[PROXY_STRING] for using a proxy server.) RAISE_INVALID_PROXY_STRING_EXCEPTION = True +# Default browser coordinates when opening new windows for tests. +WINDOW_START_X = 20 +WINDOW_START_Y = 54 + # Default browser resolutions when opening new windows for tests. # (Headless resolutions take priority, and include all browsers.) # (Firefox starts maximized by default when running in GUI Mode.) diff --git a/seleniumbase/console_scripts/sb_behave_gui.py b/seleniumbase/console_scripts/sb_behave_gui.py index 2708d6e36c0..6902c46ad39 100644 --- a/seleniumbase/console_scripts/sb_behave_gui.py +++ b/seleniumbase/console_scripts/sb_behave_gui.py @@ -16,16 +16,16 @@ import colorama import subprocess import sys +import tkinter as tk +from seleniumbase.fixtures import shared_utils +from tkinter.scrolledtext import ScrolledText -if sys.version_info <= (3, 7): +if sys.version_info <= (3, 8): current_version = ".".join(str(ver) for ver in sys.version_info[:3]) raise Exception( - "\n* SBase Commander requires Python 3.7 or newer!" + "\n* SBase Commander requires Python 3.8 or newer!" "\n** You are currently using Python %s" % current_version ) -from seleniumbase.fixtures import shared_utils -import tkinter as tk # noqa: E402 -from tkinter.scrolledtext import ScrolledText # noqa: E402 def set_colors(use_colors): diff --git a/seleniumbase/console_scripts/sb_caseplans.py b/seleniumbase/console_scripts/sb_caseplans.py index 57e287c08b4..63c5a3a0aae 100644 --- a/seleniumbase/console_scripts/sb_caseplans.py +++ b/seleniumbase/console_scripts/sb_caseplans.py @@ -20,17 +20,17 @@ import os import subprocess import sys +import tkinter as tk +from seleniumbase.fixtures import shared_utils +from tkinter import messagebox +from tkinter.scrolledtext import ScrolledText -if sys.version_info <= (3, 7): +if sys.version_info <= (3, 8): current_version = ".".join(str(ver) for ver in sys.version_info[:3]) raise Exception( - "\n* SBase Case Plans Generator requires Python 3.7 or newer!" + "\n* SBase Case Plans Generator requires Python 3.8 or newer!" "\n** You are currently using Python %s" % current_version ) -from seleniumbase.fixtures import shared_utils -import tkinter as tk # noqa: E402 -from tkinter import messagebox # noqa: E402 -from tkinter.scrolledtext import ScrolledText # noqa: E402 def set_colors(use_colors): diff --git a/seleniumbase/console_scripts/sb_commander.py b/seleniumbase/console_scripts/sb_commander.py index bc23cc6985c..a5a15b8d434 100644 --- a/seleniumbase/console_scripts/sb_commander.py +++ b/seleniumbase/console_scripts/sb_commander.py @@ -21,16 +21,16 @@ import os import subprocess import sys +import tkinter as tk +from seleniumbase.fixtures import shared_utils +from tkinter.scrolledtext import ScrolledText -if sys.version_info <= (3, 7): +if sys.version_info <= (3, 8): current_version = ".".join(str(ver) for ver in sys.version_info[:3]) raise Exception( - "\n* SBase Commander requires Python 3.7 or newer!" + "\n* SBase Commander requires Python 3.8 or newer!" "\n** You are currently using Python %s" % current_version ) -from seleniumbase.fixtures import shared_utils -import tkinter as tk # noqa: E402 -from tkinter.scrolledtext import ScrolledText # noqa: E402 def set_colors(use_colors): diff --git a/seleniumbase/console_scripts/sb_recorder.py b/seleniumbase/console_scripts/sb_recorder.py index 0e9e3365f67..173ee954839 100644 --- a/seleniumbase/console_scripts/sb_recorder.py +++ b/seleniumbase/console_scripts/sb_recorder.py @@ -18,23 +18,23 @@ import os import subprocess import sys +import tkinter as tk from seleniumbase import config as sb_config from seleniumbase.fixtures import page_utils from seleniumbase.fixtures import shared_utils +from tkinter import messagebox sb_config.rec_subprocess_p = None sb_config.rec_subprocess_used = False sys_executable = sys.executable if " " in sys_executable: sys_executable = "python" -if sys.version_info <= (3, 7): +if sys.version_info <= (3, 8): current_version = ".".join(str(ver) for ver in sys.version_info[:3]) raise Exception( - "\n* Recorder Desktop requires Python 3.7 or newer!" + "\n* Recorder Desktop requires Python 3.8 or newer!" "\n*** You are currently using Python %s" % current_version ) -import tkinter as tk # noqa: E402 -from tkinter import messagebox # noqa: E402 def set_colors(use_colors): diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 2a2dd82963e..03845961959 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -9,6 +9,7 @@ import types import urllib3 import warnings +from contextlib import suppress from selenium import webdriver from selenium.common.exceptions import ElementClickInterceptedException from selenium.common.exceptions import InvalidSessionIdException @@ -277,7 +278,7 @@ def chromedriver_on_path(): def get_uc_driver_version(full=False): uc_driver_version = None if os.path.exists(LOCAL_UC_DRIVER): - try: + with suppress(Exception): output = subprocess.check_output( '"%s" --version' % LOCAL_UC_DRIVER, shell=True ) @@ -292,8 +293,6 @@ def get_uc_driver_version(full=False): uc_driver_version = full_version else: uc_driver_version = output - except Exception: - pass return uc_driver_version @@ -372,7 +371,7 @@ def uc_special_open_if_cf( ): if url.startswith("http:") or url.startswith("https:"): special = False - try: + with suppress(Exception): req_get = requests_get(url, proxy_string) status_str = str(req_get.status_code) if ( @@ -384,8 +383,6 @@ def uc_special_open_if_cf( special = True if status_str == "403" or status_str == "429": time.sleep(0.06) # Forbidden / Blocked! (Wait first!) - except Exception: - pass if special: time.sleep(0.05) with driver: @@ -416,13 +413,11 @@ def uc_special_open_if_cf( "mobile": True } ) - try: + with suppress(Exception): driver.execute_cdp_cmd( 'Emulation.setDeviceMetricsOverride', set_device_metrics_override ) - except Exception: - pass if not mobile_emulator: page_actions.switch_to_window( driver, driver.window_handles[-1], 2 @@ -529,13 +524,11 @@ def uc_click( timeout=settings.SMALL_TIMEOUT, reconnect_time=None, ): - try: + with suppress(Exception): rct = float(by) # Add shortcut: driver.uc_click(selector, RCT) if not reconnect_time: reconnect_time = rct by = "css selector" - except Exception: - pass element = driver.wait_for_selector(selector, by=by, timeout=timeout) tag_name = element.tag_name if not tag_name == "span" and not tag_name == "input": # Must be "visible" @@ -574,7 +567,7 @@ def install_pyautogui_if_missing(driver): with pip_find_lock: # Prevent issues with multiple processes try: import pyautogui - try: + with suppress(Exception): use_pyautogui_ver = constants.PyAutoGUI.VER if pyautogui.__version__ != use_pyautogui_ver: del pyautogui @@ -582,8 +575,6 @@ def install_pyautogui_if_missing(driver): "pyautogui", version=use_pyautogui_ver ) import pyautogui - except Exception: - pass except Exception: print("\nPyAutoGUI required! Installing now...") shared_utils.pip_install( @@ -602,16 +593,32 @@ def install_pyautogui_if_missing(driver): and not (sb_config.headless or sb_config.headless2) ): from sbvirtualdisplay import Display - try: + xvfb_width = 1366 + xvfb_height = 768 + if ( + hasattr(sb_config, "_xvfb_width") + and sb_config._xvfb_width + and isinstance(sb_config._xvfb_width, int) + and hasattr(sb_config, "_xvfb_height") + and sb_config._xvfb_height + and isinstance(sb_config._xvfb_height, int) + ): + xvfb_width = sb_config._xvfb_width + xvfb_height = sb_config._xvfb_height + if xvfb_width < 1024: + xvfb_width = 1024 + sb_config._xvfb_width = xvfb_width + if xvfb_height < 768: + xvfb_height = 768 + sb_config._xvfb_height = xvfb_height + with suppress(Exception): xvfb_display = Display( visible=True, - size=(1366, 768), + size=(xvfb_width, xvfb_height), backend="xvfb", use_xauth=True, ) xvfb_display.start() - except Exception: - pass def get_configured_pyautogui(pyautogui_copy): @@ -778,12 +785,10 @@ def uc_gui_click_x_y(driver, x, y, timeframe=0.25): y = y * width_ratio _uc_gui_click_x_y(driver, x, y, timeframe=timeframe, uc_lock=False) return - try: + with suppress(Exception): page_actions.switch_to_window( driver, driver.current_window_handle, 2, uc_lock=False ) - except Exception: - pass _uc_gui_click_x_y(driver, x, y, timeframe=timeframe, uc_lock=False) @@ -986,10 +991,8 @@ def _uc_gui_click_captcha( driver.uc_open_with_disconnect(driver.current_url, 3.8) else: driver.disconnect() - try: - _uc_gui_click_x_y(driver, x, y, timeframe=0.54321) - except Exception: - pass + with suppress(Exception): + _uc_gui_click_x_y(driver, x, y, timeframe=0.32) reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6 if IS_LINUX: reconnect_time = constants.UC.RECONNECT_TIME + 0.2 @@ -1031,12 +1034,12 @@ def _uc_gui_click_captcha( return if blind: driver.uc_open_with_disconnect(driver.current_url, 3.8) - _uc_gui_click_x_y(driver, x, y, timeframe=1.05) + _uc_gui_click_x_y(driver, x, y, timeframe=0.32) else: driver.uc_open_with_reconnect(driver.current_url, 3.8) if _on_a_captcha_page(driver): driver.disconnect() - _uc_gui_click_x_y(driver, x, y, timeframe=1.05) + _uc_gui_click_x_y(driver, x, y, timeframe=0.32) driver.reconnect(reconnect_time) @@ -1216,13 +1219,11 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): and sb_config._saved_cf_tab_count ): driver.uc_open_with_disconnect(driver.current_url, 3.8) - try: + with suppress(Exception): for i in range(sb_config._saved_cf_tab_count): pyautogui.press("\t") time.sleep(0.027) pyautogui.press(" ") - except Exception: - pass else: driver.disconnect() pyautogui.press(" ") @@ -1669,18 +1670,49 @@ def _set_chrome_options( chrome_options.add_experimental_option( "mobileEmulation", emulator_settings ) + # Handle Window Position + if (headless or headless2) and IS_WINDOWS: + # https://stackoverflow.com/a/78999088/7058266 + chrome_options.add_argument("--window-position=-2400,-2400") + else: + if ( + hasattr(settings, "WINDOW_START_X") + and isinstance(settings.WINDOW_START_X, int) + and hasattr(settings, "WINDOW_START_Y") + and isinstance(settings.WINDOW_START_Y, int) + ): + chrome_options.add_argument( + "--window-position=%s,%s" % ( + settings.WINDOW_START_X, settings.WINDOW_START_Y + ) + ) + # Handle Window Size if headless or headless2: - chrome_options.add_argument( - "--window-size=%s,%s" % ( - settings.HEADLESS_START_WIDTH, settings.HEADLESS_START_HEIGHT + if ( + hasattr(settings, "HEADLESS_START_WIDTH") + and isinstance(settings.HEADLESS_START_WIDTH, int) + and hasattr(settings, "HEADLESS_START_HEIGHT") + and isinstance(settings.HEADLESS_START_HEIGHT, int) + ): + chrome_options.add_argument( + "--window-size=%s,%s" % ( + settings.HEADLESS_START_WIDTH, + settings.HEADLESS_START_HEIGHT, + ) ) - ) else: - chrome_options.add_argument( - "--window-size=%s,%s" % ( - settings.CHROME_START_WIDTH, settings.CHROME_START_HEIGHT + if ( + hasattr(settings, "CHROME_START_WIDTH") + and isinstance(settings.CHROME_START_WIDTH, int) + and hasattr(settings, "CHROME_START_HEIGHT") + and isinstance(settings.CHROME_START_HEIGHT, int) + ): + chrome_options.add_argument( + "--window-size=%s,%s" % ( + settings.CHROME_START_WIDTH, + settings.CHROME_START_HEIGHT, + ) ) - ) if ( not proxy_auth and not disable_csp @@ -1858,8 +1890,12 @@ def _set_chrome_options( binary_location = binary_loc extra_disabled_features = [] if chromium_arg: - # Can be a comma-separated list of Chromium args - chromium_arg_list = chromium_arg.split(",") + # Can be a comma-separated list of Chromium args or a list + chromium_arg_list = None + if isinstance(chromium_arg, (list, tuple)): + chromium_arg_list = chromium_arg + else: + chromium_arg_list = chromium_arg.split(",") for chromium_arg_item in chromium_arg_list: chromium_arg_item = chromium_arg_item.strip() if not chromium_arg_item.startswith("--"): @@ -1868,13 +1904,11 @@ def _set_chrome_options( else: chromium_arg_item = "--" + chromium_arg_item if "remote-debugging-port=" in chromium_arg_item: - try: + with suppress(Exception): # Extra processing for UC Mode chrome_options._remote_debugging_port = int( chromium_arg_item.split("remote-debugging-port=")[1] ) - except Exception: - pass if "set-binary" in chromium_arg_item and not binary_location: br_app = "google-chrome" binary_loc = detect_b_ver.get_binary_location( @@ -2526,14 +2560,12 @@ def get_remote_driver( try: from seleniumwire import webdriver import blinker - try: + with suppress(Exception): use_blinker_ver = constants.SeleniumWire.BLINKER_VER if blinker.__version__ != use_blinker_ver: shared_utils.pip_install( "blinker", version=use_blinker_ver ) - except Exception: - pass del blinker except Exception: shared_utils.pip_install( @@ -2957,14 +2989,12 @@ def get_local_driver( try: from seleniumwire import webdriver import blinker - try: + with suppress(Exception): use_blinker_ver = constants.SeleniumWire.BLINKER_VER if blinker.__version__ != use_blinker_ver: shared_utils.pip_install( "blinker", version=use_blinker_ver ) - except Exception: - pass del blinker except Exception: shared_utils.pip_install( @@ -3255,7 +3285,7 @@ def get_local_driver( edge_driver_version = None edgedriver_upgrade_needed = False if os.path.exists(LOCAL_EDGEDRIVER): - try: + with suppress(Exception): output = subprocess.check_output( '"%s" --version' % LOCAL_EDGEDRIVER, shell=True ) @@ -3281,8 +3311,6 @@ def get_local_driver( edge_driver_version = output if driver_version == "keep": driver_version = edge_driver_version - except Exception: - pass use_version = find_edgedriver_version_to_use( use_version, driver_version ) @@ -3386,7 +3414,7 @@ def get_local_driver( edge_options.add_argument("--headless=new") elif headless and undetectable: # (For later: UC Mode doesn't support Edge now) - try: + with suppress(Exception): if int(use_version) >= 109: edge_options.add_argument("--headless=new") elif ( @@ -3396,8 +3424,6 @@ def get_local_driver( edge_options.add_argument("--headless=chrome") else: pass # Will need Xvfb on Linux - except Exception: - pass elif headless: if "--headless" not in edge_options.arguments: edge_options.add_argument("--headless") @@ -3422,20 +3448,49 @@ def get_local_driver( edge_options.add_experimental_option( "mobileEmulation", emulator_settings ) + # Handle Window Position + if (headless or headless2) and IS_WINDOWS: + # https://stackoverflow.com/a/78999088/7058266 + edge_options.add_argument("--window-position=-2400,-2400") + else: + if ( + hasattr(settings, "WINDOW_START_X") + and isinstance(settings.WINDOW_START_X, int) + and hasattr(settings, "WINDOW_START_Y") + and isinstance(settings.WINDOW_START_Y, int) + ): + edge_options.add_argument( + "--window-position=%s,%s" % ( + settings.WINDOW_START_X, settings.WINDOW_START_Y + ) + ) + # Handle Window Size if headless or headless2: - edge_options.add_argument( - "--window-size=%s,%s" % ( - settings.HEADLESS_START_WIDTH, - settings.HEADLESS_START_HEIGHT, + if ( + hasattr(settings, "HEADLESS_START_WIDTH") + and isinstance(settings.HEADLESS_START_WIDTH, int) + and hasattr(settings, "HEADLESS_START_HEIGHT") + and isinstance(settings.HEADLESS_START_HEIGHT, int) + ): + edge_options.add_argument( + "--window-size=%s,%s" % ( + settings.HEADLESS_START_WIDTH, + settings.HEADLESS_START_HEIGHT, + ) ) - ) else: - edge_options.add_argument( - "--window-size=%s,%s" % ( - settings.CHROME_START_WIDTH, - settings.CHROME_START_HEIGHT, + if ( + hasattr(settings, "CHROME_START_WIDTH") + and isinstance(settings.CHROME_START_WIDTH, int) + and hasattr(settings, "CHROME_START_HEIGHT") + and isinstance(settings.CHROME_START_HEIGHT, int) + ): + edge_options.add_argument( + "--window-size=%s,%s" % ( + settings.CHROME_START_WIDTH, + settings.CHROME_START_HEIGHT, + ) ) - ) if user_data_dir and not is_using_uc(undetectable, browser_name): abs_path = os.path.abspath(user_data_dir) edge_options.add_argument("--user-data-dir=%s" % abs_path) @@ -3569,7 +3624,11 @@ def get_local_driver( set_binary = False if chromium_arg: # Can be a comma-separated list of Chromium args - chromium_arg_list = chromium_arg.split(",") + chromium_arg_list = None + if isinstance(chromium_arg, (list, tuple)): + chromium_arg_list = chromium_arg + else: + chromium_arg_list = chromium_arg.split(",") for chromium_arg_item in chromium_arg_list: chromium_arg_item = chromium_arg_item.strip() if not chromium_arg_item.startswith("--"): @@ -3664,19 +3723,15 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with edgedriver_fixing_lock: - try: + with suppress(Exception): if not _was_driver_repaired(): _repair_edgedriver(edge_version) _mark_driver_repaired() - except Exception: - pass else: - try: + with suppress(Exception): if not _was_driver_repaired(): _repair_edgedriver(edge_version) _mark_driver_repaired() - except Exception: - pass driver = Edge(service=service, options=edge_options) return extend_driver(driver) elif browser_name == constants.Browser.SAFARI: @@ -3828,7 +3883,7 @@ def get_local_driver( ch_driver_version = None path_chromedriver = chromedriver_on_path() if os.path.exists(LOCAL_CHROMEDRIVER): - try: + with suppress(Exception): output = subprocess.check_output( '"%s" --version' % LOCAL_CHROMEDRIVER, shell=True ) @@ -3842,8 +3897,6 @@ def get_local_driver( ch_driver_version = output if driver_version == "keep": driver_version = ch_driver_version - except Exception: - pass elif path_chromedriver: try: make_driver_executable_if_not(path_chromedriver) @@ -3852,7 +3905,7 @@ def get_local_driver( "\nWarning: Could not make chromedriver" " executable: %s" % e ) - try: + with suppress(Exception): output = subprocess.check_output( '"%s" --version' % path_chromedriver, shell=True ) @@ -3866,8 +3919,6 @@ def get_local_driver( ch_driver_version = output if driver_version == "keep": use_version = ch_driver_version - except Exception: - pass disable_build_check = True uc_driver_version = None if is_using_uc(undetectable, browser_name): @@ -4060,7 +4111,7 @@ def get_local_driver( if IS_ARM_MAC and use_uc: intel_for_uc = True # Use Intel driver for UC Mode if os.path.exists(LOCAL_CHROMEDRIVER): - try: + with suppress(Exception): output = subprocess.check_output( '"%s" --version' % LOCAL_CHROMEDRIVER, shell=True, @@ -4073,8 +4124,6 @@ def get_local_driver( output = full_ch_driver_version.split(".")[0] if int(output) >= 2: ch_driver_version = output - except Exception: - pass if ( ( not use_uc @@ -4212,7 +4261,7 @@ def get_local_driver( chrome_options.add_argument( "--user-agent=%s" % user_agent ) - try: + with suppress(Exception): if ( ( not user_agent @@ -4318,7 +4367,7 @@ def get_local_driver( service=service, options=headless_options, ) - try: + with suppress(Exception): user_agent = driver.execute_script( "return navigator.userAgent;" ) @@ -4340,11 +4389,7 @@ def get_local_driver( "--user-agent=%s" % user_agent ) sb_config.uc_agent_cache = user_agent - except Exception: - pass driver.quit() - except Exception: - pass uc_path = None if os.path.exists(LOCAL_UC_DRIVER): uc_path = LOCAL_UC_DRIVER @@ -4667,13 +4712,11 @@ def get_local_driver( "mobile": True } ) - try: + with suppress(Exception): driver.execute_cdp_cmd( 'Emulation.setDeviceMetricsOverride', set_device_metrics_override ) - except Exception: - pass return extend_driver(driver) else: # Running headless on Linux (and not using --uc) try: @@ -4719,23 +4762,19 @@ def get_local_driver( ) with chromedr_fixing_lock: if not _was_driver_repaired(): - try: + with suppress(Exception): _repair_chromedriver( chrome_options, chrome_options, mcv ) _mark_driver_repaired() - except Exception: - pass else: if not _was_driver_repaired(): - try: + with suppress(Exception): _repair_chromedriver( chrome_options, chrome_options, mcv ) - except Exception: - pass _mark_driver_repaired() - try: + with suppress(Exception): service = ChromeService( log_output=os.devnull, service_args=["--disable-build-check"], @@ -4745,8 +4784,6 @@ def get_local_driver( options=chrome_options, ) return extend_driver(driver) - except Exception: - pass # Use the virtual display on Linux during headless errors logging.debug( "\nWarning: Chrome failed to launch in" @@ -4767,14 +4804,12 @@ def get_local_driver( if is_using_uc(undetectable, browser_name): raise # Try again if Chrome didn't launch - try: + with suppress(Exception): service = ChromeService(service_args=["--disable-build-check"]) driver = webdriver.Chrome( service=service, options=chrome_options ) return extend_driver(driver) - except Exception: - pass if user_data_dir: print("\nUnable to set user_data_dir while starting Chrome!\n") raise diff --git a/seleniumbase/core/mysql.py b/seleniumbase/core/mysql.py index 1a2edb6ce4d..a1bf47ddc84 100644 --- a/seleniumbase/core/mysql.py +++ b/seleniumbase/core/mysql.py @@ -35,10 +35,7 @@ def __init__(self, database_env="test", conf_creds=None): import cryptography # noqa: F401 import pymysql except Exception: - if sys.version_info < (3, 7): - shared_utils.pip_install("PyMySQL[rsa]", version="1.0.2") - else: - shared_utils.pip_install("PyMySQL[rsa]", version="1.1.0") + shared_utils.pip_install("PyMySQL[rsa]", version="1.1.1") import pymysql db_server = settings.DB_HOST db_port = settings.DB_PORT diff --git a/seleniumbase/core/recorder_helper.py b/seleniumbase/core/recorder_helper.py index 3a13729710c..40f9cef4a6f 100644 --- a/seleniumbase/core/recorder_helper.py +++ b/seleniumbase/core/recorder_helper.py @@ -422,9 +422,14 @@ def generate_sbase_code(srt_actions): ): import unicodedata - action[1][0] = unicodedata.normalize("NFKC", action[1][0]) - action[1][0] = action[1][0].replace("\n", "\\n") - action[1][0] = action[1][0].replace("\u00B6", "") + text_list = False + try: + action[1][0] = unicodedata.normalize("NFKC", action[1][0]) + action[1][0] = action[1][0].replace("\n", "\\n") + action[1][0] = action[1][0].replace("\u00B6", "") + except Exception: + text_list = True + method = "assert_text" if action[0] == "as_et": method = "assert_exact_text" @@ -437,7 +442,17 @@ def generate_sbase_code(srt_actions): elif action[0] == "da_et": method = "deferred_assert_exact_text" if action[1][1] != "html": - if '"' not in action[1][0] and '"' not in action[1][1]: + if text_list and '"' not in action[1][1]: + sb_actions.append( + 'self.%s(%s, "%s")' + % (method, action[1][0], action[1][1]) + ) + elif text_list and "'" not in action[1][1]: + sb_actions.append( + "self.%s(%s, '%s')" + % (method, action[1][0], action[1][1]) + ) + elif '"' not in action[1][0] and '"' not in action[1][1]: sb_actions.append( 'self.%s("%s", "%s")' % (method, action[1][0], action[1][1]) @@ -458,7 +473,11 @@ def generate_sbase_code(srt_actions): % (method, action[1][0], action[1][1]) ) else: - if '"' not in action[1][0]: + if text_list: + sb_actions.append( + 'self.%s(%s)' % (method, action[1][0]) + ) + elif '"' not in action[1][0]: sb_actions.append( 'self.%s("%s")' % (method, action[1][0]) ) diff --git a/seleniumbase/core/sb_driver.py b/seleniumbase/core/sb_driver.py index 3ca7a4955f1..02dd0de4ab0 100644 --- a/seleniumbase/core/sb_driver.py +++ b/seleniumbase/core/sb_driver.py @@ -1,4 +1,5 @@ """Add new methods to extend the driver""" +from contextlib import suppress from selenium.webdriver.remote.webelement import WebElement from seleniumbase.fixtures import js_utils from seleniumbase.fixtures import page_actions @@ -36,10 +37,8 @@ def locator(self, selector, by=None): selector, by = page_utils.swap_selector_and_by_if_reversed( selector, by ) - try: + with suppress(Exception): return self.driver.default_find_element(by=by, value=selector) - except Exception: - pass raise Exception('No such Element: {%s} (by="%s")!' % (selector, by)) def get_attribute(self, selector, attribute, by="css selector"): diff --git a/seleniumbase/core/settings_parser.py b/seleniumbase/core/settings_parser.py index c45a20e748b..c13b8cc84c4 100644 --- a/seleniumbase/core/settings_parser.py +++ b/seleniumbase/core/settings_parser.py @@ -113,6 +113,10 @@ def set_settings(settings_file): settings.RAISE_INVALID_PROXY_STRING_EXCEPTION = override_settings[ key ] + elif key == "WINDOW_START_X": + settings.WINDOW_START_X = override_settings[key] + elif key == "WINDOW_START_Y": + settings.WINDOW_START_Y = override_settings[key] elif key == "CHROME_START_WIDTH": settings.CHROME_START_WIDTH = override_settings[key] elif key == "CHROME_START_HEIGHT": diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 241d4f37300..e8a81c4a002 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -33,6 +33,7 @@ def test_anything(self): Code becomes greatly simplified and easier to maintain.""" import codecs +import colorama import fasteners import json import logging @@ -45,7 +46,7 @@ def test_anything(self): import time import unittest import urllib3 -from contextlib import contextmanager +from contextlib import contextmanager, suppress from selenium.common.exceptions import ( ElementClickInterceptedException as ECI_Exception, ElementNotInteractableException as ENI_Exception, @@ -57,6 +58,7 @@ def test_anything(self): TimeoutException, WebDriverException, ) +from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.remote_connection import LOGGER @@ -73,9 +75,11 @@ def test_anything(self): VisualException, ) from seleniumbase.config import settings +from seleniumbase.core import browser_launcher from seleniumbase.core import download_helper from seleniumbase.core import log_helper from seleniumbase.core import session_helper +from seleniumbase.core import visual_helper from seleniumbase.fixtures import constants from seleniumbase.fixtures import css_to_xpath from seleniumbase.fixtures import js_utils @@ -180,6 +184,8 @@ def __initialize_variables(self): self._chart_series_count = {} self._tour_steps = {} self._xvfb_display = None + self._xvfb_width = None + self._xvfb_height = None @classmethod def main(self, name, file, *args): @@ -221,10 +227,8 @@ def open(self, url): if self.__needs_minimum_wait(): time.sleep(0.04) pre_action_url = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass url = str(url).strip() # Remove leading and trailing whitespace if not self.__looks_like_a_page_url(url): # url should start with one of the following: @@ -352,18 +356,12 @@ def open(self, url): if self.undetectable: self.__uc_frame_layer = 0 if self.demo_mode: - if ( - self.driver.current_url.startswith("http") - or self.driver.current_url.startswith("file") - or self.driver.current_url.startswith("data") - ): + if self.driver.current_url.startswith(("http", "file", "data")): if not js_utils.is_jquery_activated(self.driver): - try: + with suppress(Exception): js_utils.add_js_link( self.driver, constants.JQuery.MIN_JS ) - except Exception: - pass self.__demo_mode_pause_if_active() def get(self, url): @@ -421,10 +419,8 @@ def click( if scroll and not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) pre_action_url = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass pre_window_count = len(self.driver.window_handles) try: if ( @@ -438,7 +434,7 @@ def click( href = None new_tab = False onclick = None - try: + with suppress(Exception): if self.headless and element.tag_name.lower() == "a": # Handle a special case of opening a new tab (headless) href = element.get_attribute("href").strip() @@ -448,20 +444,14 @@ def click( new_tab = True if new_tab and self.__looks_like_a_page_url(href): if onclick: - try: + with suppress(Exception): self.execute_script(onclick) - except Exception: - pass current_window = self.driver.current_window_handle self.open_new_window() - try: + with suppress(Exception): self.open(href) - except Exception: - pass self.switch_to_window(current_window) return - except Exception: - pass # Normal click self.__element_click(element) except Stale_Exception: @@ -474,10 +464,8 @@ def click( timeout=timeout, original_selector=original_selector, ) - try: + with suppress(Exception): self.__scroll_to_element(element, selector, by) - except Exception: - pass if self.browser == "safari" and by == By.LINK_TEXT: self.__jquery_click(selector, by=by) elif self.browser == "safari": @@ -485,7 +473,7 @@ def click( else: self.__element_click(element) except ENI_Exception as e: - try: + with suppress(Exception): if ( "element has zero size" in e.msg and element.tag_name.lower() == "a" @@ -497,8 +485,6 @@ def click( if self.__needs_minimum_wait(): time.sleep(0.04) return - except Exception: - pass self.wait_for_ready_state_complete() time.sleep(0.1) element = page_actions.wait_for_element_visible( @@ -511,12 +497,10 @@ def click( if not page_actions.is_element_clickable( self.driver, selector, by ): - try: + with suppress(Exception): self.wait_for_element_clickable( selector, by, timeout=1.8 ) - except Exception: - pass # Find out which element would get the click instead element = page_actions.wait_for_element_visible( self.driver, selector, @@ -527,7 +511,7 @@ def click( href = None new_tab = False onclick = None - try: + with suppress(Exception): if element.tag_name.lower() == "a": # Handle a special case of opening a new tab (non-headless) href = element.get_attribute("href").strip() @@ -537,20 +521,14 @@ def click( new_tab = True if new_tab and self.__looks_like_a_page_url(href): if onclick: - try: + with suppress(Exception): self.execute_script(onclick) - except Exception: - pass current_window = self.driver.current_window_handle self.open_new_window() - try: + with suppress(Exception): self.open(href) - except Exception: - pass self.switch_to_window(current_window) return - except Exception: - pass if scroll and not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) if self.browser == "firefox" or self.browser == "safari": @@ -632,10 +610,8 @@ def click( self.switch_to_window(-1) if settings.WAIT_FOR_RSC_ON_CLICKS: if not self.undetectable: - try: + with suppress(Exception): self.wait_for_ready_state_complete() - except Exception: - pass if self.__needs_minimum_wait() or self.browser == "safari": time.sleep(0.05) else: @@ -643,10 +619,8 @@ def click( else: if not self.undetectable: # A smaller subset of self.wait_for_ready_state_complete() - try: + with suppress(Exception): self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT) - except Exception: - pass if self.__needs_minimum_wait() or self.browser == "safari": time.sleep(0.045) try: @@ -656,10 +630,8 @@ def click( if self.__needs_minimum_wait(): time.sleep(0.075) except Exception: - try: + with suppress(Exception): self.wait_for_ready_state_complete() - except Exception: - pass if self.__needs_minimum_wait(): time.sleep(0.05) else: @@ -697,8 +669,6 @@ def slow_click(self, selector, by="css selector", timeout=None): self.click(selector, by=by, timeout=timeout, delay=0.25) def double_click(self, selector, by="css selector", timeout=None): - from selenium.webdriver.common.action_chains import ActionChains - self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -729,10 +699,8 @@ def double_click(self, selector, by="css selector", timeout=None): original_selector=original_selector, ) pre_action_url = None - try: + with suppress(Exception): 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 @@ -782,8 +750,6 @@ def double_click(self, selector, by="css selector", timeout=None): def context_click(self, selector, by="css selector", timeout=None): """(A context click is a right-click that opens a context menu.)""" - from selenium.webdriver.common.action_chains import ActionChains - self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -814,10 +780,8 @@ def context_click(self, selector, by="css selector", timeout=None): original_selector=original_selector, ) pre_action_url = None - try: + with suppress(Exception): 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 @@ -935,19 +899,15 @@ def update_text( element = self.wait_for_element_clickable( selector, by=by, timeout=timeout ) - try: + with suppress(Exception): element.clear() - except Exception: - pass # Clearing the text field first might not be necessary except Exception: pass # Clearing the text field first might not be necessary self.__demo_mode_pause_if_active(tiny=True) pre_action_url = None if self.demo_mode: - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass text = self.__get_type_checked_text(text) try: if not text.endswith("\n"): @@ -1057,10 +1017,8 @@ def add_text(self, selector, text, by="css selector", timeout=None): self.__scroll_to_element(element, selector, by) pre_action_url = None if self.demo_mode: - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass text = self.__get_type_checked_text(text) try: if not text.endswith("\n"): @@ -1233,11 +1191,9 @@ def clear(self, selector, by="css selector", timeout=None): selector, by=by, timeout=timeout ) element.clear() - try: + with suppress(Exception): backspaces = Keys.BACK_SPACE * 42 # Autofill Defense element.send_keys(backspaces) - except Exception: - pass except Exception: element.clear() @@ -1334,17 +1290,13 @@ def go_back(self): if hasattr(self, "recorder_mode") and self.recorder_mode: self.save_recorded_actions() pre_action_url = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass self.__last_page_load_url = None self.driver.back() - try: + with suppress(Exception): 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() @@ -1424,6 +1376,7 @@ def open_if_not_url(self, url): self.open(url) def is_element_present(self, selector, by="css selector"): + """Returns whether the element exists in the HTML.""" self.wait_for_ready_state_complete() selector, by = self.__recalculate_selector(selector, by) if self.__is_shadow_selector(selector): @@ -1431,6 +1384,7 @@ def is_element_present(self, selector, by="css selector"): return page_actions.is_element_present(self.driver, selector, by) def is_element_visible(self, selector, by="css selector"): + """Returns whether the element is visible on the page.""" self.wait_for_ready_state_complete() selector, by = self.__recalculate_selector(selector, by) if self.__is_shadow_selector(selector): @@ -1452,6 +1406,7 @@ def is_element_enabled(self, selector, by="css selector"): return page_actions.is_element_enabled(self.driver, selector, by) def is_text_visible(self, text, selector="html", by="css selector"): + """Returns whether the text substring is visible in the element.""" self.wait_for_ready_state_complete() time.sleep(0.01) selector, by = self.__recalculate_selector(selector, by) @@ -1460,6 +1415,8 @@ def is_text_visible(self, text, selector="html", by="css selector"): return page_actions.is_text_visible(self.driver, text, selector, by) def is_exact_text_visible(self, text, selector="html", by="css selector"): + """Returns whether the text is exactly equal to the element text. + (Leading and trailing whitespace is ignored in the verification.)""" self.wait_for_ready_state_complete() time.sleep(0.01) selector, by = self.__recalculate_selector(selector, by) @@ -1498,6 +1455,7 @@ def is_attribute_present( ) def is_link_text_visible(self, link_text): + """Returns whether there's an exact match for the link text.""" self.wait_for_ready_state_complete() time.sleep(0.01) return page_actions.is_element_visible( @@ -1505,6 +1463,7 @@ def is_link_text_visible(self, link_text): ) def is_partial_link_text_visible(self, partial_link_text): + """Returns whether there's a substring match for the link text.""" self.wait_for_ready_state_complete() time.sleep(0.01) return page_actions.is_element_visible( @@ -1633,10 +1592,8 @@ def click_link_text(self, link_text, timeout=None): if self.__needs_minimum_wait(): time.sleep(0.04) pre_action_url = None - try: + with suppress(Exception): 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) @@ -1707,10 +1664,8 @@ def click_link_text(self, link_text, timeout=None): # switch to the last one if it exists. self.switch_to_window(-1) if settings.WAIT_FOR_RSC_ON_PAGE_LOADS: - try: + with suppress(Exception): self.wait_for_ready_state_complete() - except Exception: - pass if self.demo_mode: if self.driver.current_url != pre_action_url: if not js_utils.is_jquery_activated(self.driver): @@ -1736,10 +1691,8 @@ def click_partial_link_text(self, partial_link_text, timeout=None): partial_link_text, timeout=timeout ) pre_action_url = None - try: + with suppress(Exception): 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( @@ -1822,10 +1775,8 @@ def click_partial_link_text(self, partial_link_text, timeout=None): # switch to the last one if it exists. self.switch_to_window(-1) if settings.WAIT_FOR_RSC_ON_PAGE_LOADS: - try: + with suppress(Exception): self.wait_for_ready_state_complete() - except Exception: - pass if self.demo_mode: if self.driver.current_url != pre_action_url: if not js_utils.is_jquery_activated(self.driver): @@ -1941,10 +1892,8 @@ def set_attribute( original_attribute = attribute original_value = value if scroll and self.is_element_visible(selector, by=by): - try: + with suppress(Exception): self.scroll_to(selector, by=by, timeout=timeout) - except Exception: - pass attribute = re.escape(attribute) attribute = self.__escape_quotes_if_needed(attribute) value = re.escape(value) @@ -1989,10 +1938,8 @@ def set_attributes(self, selector, attribute, value, by="css selector"): attribute, value, ) - try: + with suppress(Exception): self.execute_script(script) - 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();") @@ -2021,10 +1968,8 @@ def remove_attribute( timeout = self.__get_new_timeout(timeout) selector, by = self.__recalculate_selector(selector, by) if self.is_element_visible(selector, by=by): - try: + with suppress(Exception): self.scroll_to(selector, by=by, timeout=timeout) - except Exception: - pass attribute = re.escape(attribute) attribute = self.__escape_quotes_if_needed(attribute) css_selector = self.convert_to_css_selector(selector, by=by) @@ -2053,10 +1998,8 @@ def remove_attributes(self, selector, attribute, by="css selector"): css_selector, attribute, ) - try: + with suppress(Exception): self.execute_script(script) - except Exception: - pass def get_property( self, selector, property, by="css selector", timeout=None @@ -2202,20 +2145,16 @@ def click_visible_elements( element = self.wait_for_element_present( selector, by=by, timeout=timeout ) - try: + with suppress(Exception): # If the first element isn't visible, wait a little. if not element.is_displayed(): time.sleep(0.16) if self.undetectable: time.sleep(0.06) - except Exception: - pass elements = self.find_elements(selector, by=by) pre_action_url = None - try: + with suppress(Exception): 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: @@ -2298,10 +2237,8 @@ def click_nth_visible_element( number = 0 element = elements[number] pre_action_url = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass pre_window_count = len(self.driver.window_handles) try: self.__scroll_to_element(element) @@ -2344,22 +2281,18 @@ def click_if_visible(self, selector, by="css selector", timeout=0): if self.is_element_visible(selector, by=by): self.click(selector, by=by) elif timeout > 0: - try: + with suppress(Exception): self.wait_for_element_visible( selector, by=by, timeout=timeout ) - except Exception: - pass if self.is_element_visible(selector, by=by): self.click(selector, by=by) def click_active_element(self): self.wait_for_ready_state_complete() pre_action_url = None - try: + with suppress(Exception): 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) @@ -2492,12 +2425,10 @@ def check_if_unchecked(self, selector, by="css selector"): ) # Handle switches that sit on checkboxes with zero opacity: # Change the opacity a bit to allow the click to succeed. - try: + with suppress(Exception): self.execute_script( 'arguments[0].style.opacity="0.001";', element ) - except Exception: - pass if self.is_element_visible(selector, by=by): self.click(selector, by=by) else: @@ -2505,14 +2436,12 @@ def check_if_unchecked(self, selector, by="css selector"): self.__dont_record_js_click = True self.js_click(selector, by="css selector") self.__dont_record_js_click = False - try: + with suppress(Exception): self.execute_script( 'arguments[0].style.opacity="arguments[1]";', element, opacity, ) - except Exception: - pass def select_if_unselected(self, selector, by="css selector"): """Same as check_if_unchecked()""" @@ -2532,12 +2461,10 @@ def uncheck_if_checked(self, selector, by="css selector"): ) # Handle switches that sit on checkboxes with zero opacity: # Change the opacity a bit to allow the click to succeed. - try: + with suppress(Exception): self.execute_script( 'arguments[0].style.opacity="0.001";', element ) - except Exception: - pass if self.is_element_visible(selector, by=by): self.click(selector, by=by) else: @@ -2545,14 +2472,12 @@ def uncheck_if_checked(self, selector, by="css selector"): self.__dont_record_js_click = True self.js_click(selector, by="css selector") self.__dont_record_js_click = False - try: + with suppress(Exception): self.execute_script( 'arguments[0].style.opacity="arguments[1]";', element, opacity, ) - except Exception: - pass def unselect_if_selected(self, selector, by="css selector"): """Same as uncheck_if_checked()""" @@ -2611,14 +2536,12 @@ def switch_to_frame_of_element(self, selector, by="css selector"): iframe_identifier = '[class="%s"]' % iframe_class else: continue - try: + with suppress(Exception): self.switch_to_frame(iframe_identifier, timeout=1) if self.__needs_minimum_wait(): time.sleep(0.02) if self.is_element_present(selector, by=by): return iframe_identifier - except Exception: - pass self.switch_to_default_content() if self.__needs_minimum_wait(): time.sleep(0.02) @@ -2692,10 +2615,8 @@ 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 = None - try: + with suppress(Exception): 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": @@ -2759,10 +2680,8 @@ def hover_and_click( self.__switch_to_newest_window_if_not_blank() elif self.browser == "safari": # Release the hover by hovering elsewhere - try: + with suppress(Exception): page_actions.hover_on_element(self.driver, "body") - except Exception: - pass if self.demo_mode: if self.driver.current_url != pre_action_url: if not js_utils.is_jquery_activated(self.driver): @@ -2822,10 +2741,8 @@ 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 = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass pre_window_count = len(self.driver.window_handles) outdated_driver = False element = None @@ -2886,7 +2803,7 @@ def drag_and_drop( timeout=None, jquery=False, ): - """Drag and drop an element from one selector to another.""" + """Drag-and-drop an element from one selector to another.""" self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -2933,7 +2850,7 @@ def drag_and_drop( def drag_and_drop_with_offset( self, selector, x, y, by="css selector", timeout=None ): - """Drag and drop an element to an {X,Y}-offset location.""" + """Drag-and-drop an element to an {X,Y}-offset location.""" self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -3013,10 +2930,8 @@ def __select_option( dropdown_selector, dropdown_by ) pre_action_url = None - try: + with suppress(Exception): pre_action_url = self.driver.current_url - except Exception: - pass pre_window_count = len(self.driver.window_handles) try: if option_by == "index": @@ -3291,10 +3206,8 @@ def load_html_string(self, html_string, new_page=True): self.open("data:text/html,
") inner_head = """document.getElementsByTagName("head")[0].innerHTML""" inner_body = """document.getElementsByTagName("body")[0].innerHTML""" - try: + with suppress(Exception): self.wait_for_element_present("body", timeout=1) - except Exception: - pass if not found_body: self.execute_script('''%s = \"%s\"''' % (inner_body, html_string)) elif found_body and not found_head: @@ -4096,8 +4009,6 @@ def get_new_driver( "Valid options = {%s}" % (browser, valid_browsers) ) # Launch a web browser - from seleniumbase.core import browser_launcher - new_driver = browser_launcher.get_driver( browser_name=browser_name, headless=headless, @@ -4193,7 +4104,8 @@ def get_new_driver( self.driver.maximize_window() self.wait_for_ready_state_complete() else: - self.driver.set_window_size(width, height) + with suppress(Exception): + self.driver.set_window_size(width, height) except Exception: pass # Keep existing browser resolution elif self.browser == "safari": @@ -4204,10 +4116,8 @@ def get_new_driver( except Exception: pass # Keep existing browser resolution else: - try: - self.driver.set_window_rect(10, 20, width, height) - except Exception: - pass + with suppress(Exception): + self.driver.set_window_rect(10, 46, width, height) if self.start_page and len(self.start_page) >= 4: if page_utils.is_valid_url(self.start_page): self.open(self.start_page) @@ -4655,14 +4565,17 @@ def activate_recorder(self): from seleniumbase.js_code.recorder_js import recorder_js if not self.is_chromium(): + if "linux" not in sys.platform: + c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX + c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX + cr = colorama.Style.RESET_ALL + sc = c1 + "Selenium" + c2 + "Base" + cr raise Exception( - "The Recorder is only for Chromium browsers: (Chrome or Edge)" + "The %s Recorder is for Chromium only!\n" + " (Supported browsers: Chrome and Edge)" % sc ) url = self.driver.current_url - if ( - url.startswith("data:") or url.startswith("about:") - or url.startswith("chrome:") or url.startswith("edge:") - ): + if url.startswith(("data:", "about:", "chrome:", "edge:")): message = ( "The URL in Recorder-Mode cannot start with: " '"data:", "about:", "chrome:", or "edge:"!' @@ -4671,7 +4584,7 @@ def activate_recorder(self): return if self.recorder_ext: return # The Recorder extension is already active - try: + with suppress(Exception): recorder_on = self.get_session_storage_item("recorder_activated") if not recorder_on == "yes": self.execute_script(recorder_js) @@ -4680,8 +4593,6 @@ def activate_recorder(self): print("\n" + message) p_msg = "Recorder Mode ACTIVE.
[ESC]: Pause. [~`]: Resume." self.post_message(p_msg, pause=False, style="error") - except Exception: - pass def __current_url_is_recordable(self): url = self.get_current_url() @@ -4721,10 +4632,7 @@ def save_recorded_actions(self): def __get_recorded_actions_on_active_tab(self): url = self.driver.current_url - if ( - url.startswith("data:") or url.startswith("about:") - or url.startswith("chrome:") or url.startswith("edge:") - ): + if url.startswith(("data:", "about:", "chrome:", "edge:")): return [] self.__origins_to_save.append(self.get_origin()) actions = self.get_session_storage_item("recorded_actions") @@ -4735,9 +4643,9 @@ def __get_recorded_actions_on_active_tab(self): return [] def __process_recorded_actions(self): + """Generates code after the SeleniumBase Recorder runs.""" if self.driver is None: return - import colorama from seleniumbase.core import recorder_helper raw_actions = [] # All raw actions from sessionStorage @@ -4939,10 +4847,7 @@ def __process_recorded_actions(self): or srt_actions[n - 1][0] == "jq_cl" or srt_actions[n - 1][0] == "jq_ca" ): - if ( - srt_actions[n - 1][1].startswith("input") - or srt_actions[n - 1][1].startswith("button") - ): + if srt_actions[n - 1][1].startswith(("input", "button")): srt_actions[n][0] = "f_url" elif srt_actions[n - 1][0] == "input": if srt_actions[n - 1][2].endswith("\n"): @@ -5290,7 +5195,6 @@ def __process_recorded_actions(self): and (filename == "base_case.py" or methodname == "runTest") ): import traceback - stack_base = traceback.format_stack()[0].split(os.sep)[-1] test_base = stack_base.split(", in ")[0] if hasattr(self, "cm_filename") and self.cm_filename: @@ -5360,11 +5264,9 @@ def __process_recorded_actions(self): if recordings_folder.endswith("/"): recordings_folder = recordings_folder[:-1] if not os.path.exists(recordings_folder): - try: + with suppress(Exception): os.makedirs(recordings_folder) sys.stdout.write("\nCreated recordings%s" % os.sep) - except Exception: - pass data = [] data.append("") @@ -5465,12 +5367,10 @@ def __process_recorded_actions(self): if not new_file: rec_message = ">>> RECORDING ADDED to: " star_len = len(rec_message) + len(file_path) - try: + with suppress(Exception): terminal_size = os.get_terminal_size().columns if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size - except Exception: - pass spc = "\n\n" if hasattr(self, "rec_print") and self.rec_print: spc = "" @@ -5543,22 +5443,16 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): if recordings_folder.endswith("/"): recordings_folder = recordings_folder[:-1] if not os.path.exists(recordings_folder): - try: + with suppress(Exception): os.makedirs(recordings_folder) - except Exception: - pass features_folder = os.path.join(recordings_folder, "features") if not os.path.exists(features_folder): - try: + with suppress(Exception): os.makedirs(features_folder) - except Exception: - pass steps_folder = os.path.join(features_folder, "steps") if not os.path.exists(steps_folder): - try: + with suppress(Exception): os.makedirs(steps_folder) - except Exception: - pass file_name = filename.split(".")[0] if hasattr(self, "is_behave") and self.is_behave: @@ -5573,12 +5467,10 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): if not new_file: rec_message = ">>> RECORDING ADDED to: " star_len = len(rec_message) + len(file_path) - try: + with suppress(Exception): terminal_size = os.get_terminal_size().columns if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size - except Exception: - pass spc = "\n" if hasattr(self, "rec_print") and self.rec_print: spc = "" @@ -5684,16 +5576,14 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): print("Created recordings/features/steps/imported.py") def bring_active_window_to_front(self): - """Brings the active browser window to the front. - This is useful when multiple drivers are being used.""" + """Brings the active browser window to the front (on top). + Useful when multiple drivers are being used at the same time.""" self.__check_scope() - try: + with suppress(Exception): if not self.__is_in_frame(): # Only bring the window to the front if not in a frame # because the driver resets itself to default content. self.switch_to_window(self.driver.current_window_handle) - except Exception: - pass def bring_to_front(self, selector, by="css selector"): """Updates the Z-index of a page element to bring it into view. @@ -5723,7 +5613,7 @@ def bring_to_front(self, selector, by="css selector"): def highlight_click( self, selector, by="css selector", loops=3, scroll=True, timeout=None, ): - """Highlights the element and then clicks it.""" + """Highlights the element, and then clicks it.""" self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -5782,10 +5672,8 @@ def __highlight_element(self, element, loops=None, scroll=True): if not loops: loops = settings.HIGHLIGHTS if scroll and self.browser != "safari": - try: + with suppress(Exception): self.__slow_scroll_to_element(element) - except Exception: - pass if self.highlights: loops = self.highlights if self.browser == "ie": @@ -5815,7 +5703,7 @@ def __highlight( self, selector, by="css selector", loops=None, scroll=True ): """This method uses fancy JavaScript to highlight an element. - (Commonly used during Demo Mode automatically)""" + (Automatically using in S_e_l_e_n_i_u_m_B_a_s_e Demo Mode)""" self.__check_scope() selector, by = self.__recalculate_selector(selector, by, xp_ok=False) element = self.wait_for_element_visible( @@ -5935,14 +5823,12 @@ def highlight_elements( count = 0 elements = self.find_elements(selector, by=by) for element in elements: - try: + with suppress(Exception): if element.is_displayed(): self.__highlight_element( element, loops=loops, scroll=scroll ) count += 1 - except Exception: - pass if limit > 0 and count >= limit: break @@ -6189,10 +6075,8 @@ def js_click( time_stamp = 0 action = ["", "", "", time_stamp] pre_action_url = None - try: + with suppress(Exception): 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();") @@ -6294,10 +6178,8 @@ def js_click( # If a click closes the active window, # switch to the last one if it exists. self.switch_to_window(-1) - try: + with suppress(Exception): self.wait_for_ready_state_complete() - except Exception: - pass self.__demo_mode_pause_if_active() def js_click_if_present(self, selector, by="css selector", timeout=0): @@ -6309,10 +6191,8 @@ def js_click_if_present(self, selector, by="css selector", timeout=0): if self.is_element_present(selector, by=by): self.js_click(selector, by=by) elif timeout > 0: - try: + with suppress(Exception): self.wait_for_element_present(selector, by=by, timeout=timeout) - except Exception: - pass if self.is_element_present(selector, by=by): self.js_click(selector, by=by) @@ -6325,10 +6205,8 @@ def js_click_if_visible(self, selector, by="css selector", timeout=0): if self.is_element_visible(selector, by=by): self.js_click(selector, by=by) elif timeout > 0: - try: + with suppress(Exception): self.wait_for_element_visible(selector, by=by, timeout=timeout) - except Exception: - pass if self.is_element_visible(selector, by=by): self.js_click(selector, by=by) @@ -6421,13 +6299,11 @@ def hide_element(self, selector, by="css selector"): """Hide the first element on the page that matches the selector.""" self.__check_scope() element = None - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) element = self.wait_for_element_present( selector, by=by, timeout=0.5 ) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector and element: @@ -6453,10 +6329,8 @@ def hide_element(self, selector, by="css selector"): def hide_elements(self, selector, by="css selector"): """Hide all elements on the page that match the selector.""" self.__check_scope() - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector: @@ -6479,11 +6353,9 @@ def show_element(self, selector, by="css selector"): """Show the first element on the page that matches the selector.""" self.__check_scope() element = None - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) element = self.wait_for_element_present(selector, by=by, timeout=1) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector and element: @@ -6509,10 +6381,8 @@ def show_element(self, selector, by="css selector"): def show_elements(self, selector, by="css selector"): """Show all elements on the page that match the selector.""" self.__check_scope() - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector: @@ -6535,13 +6405,11 @@ def remove_element(self, selector, by="css selector"): """Remove the first element on the page that matches the selector.""" self.__check_scope() element = None - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) element = self.wait_for_element_present( selector, by=by, timeout=0.5 ) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector and element: @@ -6567,10 +6435,8 @@ def remove_element(self, selector, by="css selector"): def remove_elements(self, selector, by="css selector"): """Remove all elements on the page that match the selector.""" self.__check_scope() - try: + with suppress(Exception): self.wait_for_element_visible("body", timeout=1.5) - except Exception: - pass selector, by = self.__recalculate_selector(selector, by) css_selector = self.convert_to_css_selector(selector, by=by) if ":contains(" in css_selector: @@ -6635,10 +6501,8 @@ def show_file_choosers(self): $elements[index].setAttribute('class', new_class);}""" % css_selector ) - try: + with suppress(Exception): self.execute_script(script) - 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();") @@ -6656,10 +6520,8 @@ def disable_beforeunload(self): self.is_chromium() and self.driver.current_url.startswith("http") ): - try: + with suppress(Exception): self.driver.execute_script("window.onbeforeunload=null;") - except Exception: - pass def get_domain_url(self, url): self.__check_scope() @@ -6675,12 +6537,10 @@ def get_beautiful_soup(self, source=None): from bs4 import BeautifulSoup if not source: - try: + with suppress(Exception): self.wait_for_element_visible( "body", timeout=settings.MINI_TIMEOUT ) - except Exception: - pass source = self.get_page_source() return BeautifulSoup(source, "html.parser") @@ -6693,11 +6553,9 @@ def get_unique_links(self): time.sleep(0.08) if self.undetectable: time.sleep(0.02) - try: + with suppress(Exception): self.wait_for_element_present("body", timeout=1.5) self.wait_for_element_visible("body", timeout=1.5) - except Exception: - pass if self.__needs_minimum_wait(): time.sleep(0.25) if self.undetectable: @@ -6911,12 +6769,7 @@ def get_pdf_text( try: from pdfminer.high_level import extract_text except Exception: - if not sys.version_info >= (3, 8): - shared_utils.pip_install( - "pdfminer.six", version="20221105" - ) - else: - shared_utils.pip_install("pdfminer.six") + shared_utils.pip_install("pdfminer.six") from pdfminer.high_level import extract_text if not password: password = "" @@ -7036,10 +6889,8 @@ def create_folder(self, folder): if len(folder) < 1: raise Exception("Minimum folder name length = 1.") if not os.path.exists(folder): - try: + with suppress(Exception): os.makedirs(folder) - except Exception: - pass def choose_file( self, selector, file_path, by="css selector", timeout=None @@ -7079,10 +6930,8 @@ def choose_file( self.__scroll_to_element(element, selector, by) pre_action_url = None if self.demo_mode: - try: + with suppress(Exception): 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();") @@ -7370,10 +7219,8 @@ def delete_downloaded_file_if_present(self, file, browser=False): Those paths are usually the same. (browser-dependent).""" if self.is_downloaded_file_present(file, browser=browser): file_path = self.get_path_of_downloaded_file(file, browser=browser) - try: + with suppress(Exception): os.remove(file_path) - except Exception: - pass def delete_downloaded_file(self, file, browser=False): """Same as self.delete_downloaded_file_if_present() @@ -7390,10 +7237,8 @@ def delete_downloaded_file(self, file, browser=False): Those paths are usually the same. (browser-dependent).""" if self.is_downloaded_file_present(file, browser=browser): file_path = self.get_path_of_downloaded_file(file, browser=browser) - try: + with suppress(Exception): os.remove(file_path) - except Exception: - pass def assert_downloaded_file(self, file, timeout=None, browser=False): """Asserts that the file exists in SeleniumBase's [Downloads Folder]. @@ -7450,13 +7295,11 @@ def assert_downloaded_file(self, file, timeout=None, browser=False): self.__extra_actions.append(action) if self.demo_mode: messenger_post = "ASSERT DOWNLOADED FILE: [%s]" % file - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def assert_downloaded_file_regex(self, regex, timeout=None, browser=False): """Assert the filename regex exists in SeleniumBase's Downloads Folder. @@ -7505,13 +7348,11 @@ def assert_downloaded_file_regex(self, regex, timeout=None, browser=False): messenger_post = ( "ASSERT DOWNLOADED FILE REGEX: [%s]" % regex ) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def assert_data_in_downloaded_file( self, data, file, timeout=None, browser=False @@ -7530,37 +7371,37 @@ def assert_data_in_downloaded_file( def assert_true(self, expr, msg=None): """Asserts that the expression is True. - Will raise an exception if the statement if False.""" + Raises an exception if the statement if False.""" self.assertTrue(expr, msg=msg) def assert_false(self, expr, msg=None): """Asserts that the expression is False. - Will raise an exception if the statement if True.""" + Raises an exception if the statement if True.""" self.assertFalse(expr, msg=msg) def assert_equal(self, first, second, msg=None): """Asserts that the two values are equal. - Will raise an exception if the values are not equal.""" + Raises an exception if the values are not equal.""" self.assertEqual(first, second, msg=msg) def assert_not_equal(self, first, second, msg=None): """Asserts that the two values are not equal. - Will raise an exception if the values are equal.""" + Raises an exception if the values are equal.""" self.assertNotEqual(first, second, msg=msg) def assert_in(self, first, second, msg=None): """Asserts that the first string is in the second string. - Will raise an exception if the first string is not in the second.""" + Raises an exception if the first string is not in the second.""" self.assertIn(first, second, msg=msg) def assert_not_in(self, first, second, msg=None): """Asserts that the first string is not in the second string. - Will raise an exception if the first string is in the second string.""" + Raises an exception if the first string is in the second string.""" self.assertNotIn(first, second, msg=msg) def assert_raises(self, *args, **kwargs): """Asserts that the following block of code raises an exception. - Will raise an exception if the block of code has no exception. + Raises an exception if the block of code has no exception. Usage Example => # Verify that the expected exception is raised. with self.assert_raises(Exception): @@ -8180,17 +8021,15 @@ def set_value( else: if the_type == "range" and ":contains\\(" not in css_selector: # Some input sliders need a mouse event to trigger listeners. - try: + with suppress(Exception): mouse_move_script = ( """m_elm = document.querySelector('%s');""" """m_evt = new Event('mousemove');""" """m_elm.dispatchEvent(m_evt);""" % css_selector ) self.execute_script(mouse_move_script) - except Exception: - pass elif the_type == "range" and ":contains\\(" in css_selector: - try: + with suppress(Exception): element = self.wait_for_element_present( original_selector, by=by, timeout=1 ) @@ -8200,8 +8039,6 @@ def set_value( """m_elm.dispatchEvent(m_evt);""" ) self.execute_script(mouse_move_script, element) - except Exception: - pass self.__demo_mode_pause_if_active() if not self.demo_mode and not self.slow_mode: if self.__needs_minimum_wait(): @@ -8221,13 +8058,11 @@ def js_update_text(self, selector, text, by="css selector", timeout=None): text = self.__get_type_checked_text(text) self.set_value(selector, text, by=by, timeout=timeout) if not text.endswith("\n"): - try: + with suppress(Exception): element = page_actions.wait_for_element_present( self.driver, selector, by, timeout=0.2 ) element.send_keys(" " + Keys.BACK_SPACE) - except Exception: - pass def js_type(self, selector, text, by="css selector", timeout=None): """Same as self.js_update_text() @@ -8347,13 +8182,11 @@ def jquery_update_text( ) element.send_keys(Keys.RETURN) else: - try: + with suppress(Exception): element = self.wait_for_element_present( original_selector, by=original_by, timeout=0.2 ) element.send_keys(" " + Keys.BACK_SPACE) - except Exception: - pass self.__demo_mode_pause_if_active() def jquery_type(self, selector, text, by="css selector", timeout=None): @@ -8530,10 +8363,8 @@ def console_log_script(self, script): def get_recorded_console_logs(self): """Get console logs recorded after "start_recording_console_logs()".""" logs = [] - try: + with suppress(Exception): logs = self.execute_script("return console.logs;") - except Exception: - pass return logs ############ @@ -8916,7 +8747,7 @@ def assert_element_not_present( self, selector, by="css selector", timeout=None ): """Same as self.assert_element_absent() - Will raise an exception if the element stays present. + Raises an exception if the element stays present. A hidden element counts as a present element, which fails this assert. If you want to assert that elements are hidden instead of nonexistent, use assert_element_not_visible() instead. @@ -8978,10 +8809,8 @@ def _check_browser(self): """This method raises an exception if the active window is closed. (This provides a much cleaner exception message in this situation.)""" active_window = None - try: + with suppress(Exception): active_window = self.driver.current_window_handle # Fails if None - except Exception: - pass if not active_window: raise NoSuchWindowException("Active window was already closed!") @@ -8995,10 +8824,8 @@ def _print(self, msg): """ if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded: if not isinstance(msg, str): - try: + with suppress(Exception): msg = str(msg) - except Exception: - pass sys.stderr.write(msg + "\n") else: print(msg) @@ -9412,7 +9239,7 @@ def find_element(self, selector, by="css selector", timeout=None): def assert_element(self, selector, by="css selector", timeout=None): """Similar to wait_for_element_visible(), but returns nothing. - As above, will raise an exception if nothing can be found. + As above, raises an exception if nothing can be found. Returns True if successful. Default timeout = SMALL_TIMEOUT.""" self.__check_scope() if not timeout: @@ -9452,7 +9279,7 @@ def assert_element_visible( self, selector, by="css selector", timeout=None ): """Same as self.assert_element() - As above, will raise an exception if nothing can be found.""" + As above, raises an exception if nothing can be found.""" self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT @@ -9678,6 +9505,7 @@ def assert_text( """Similar to wait_for_text_visible() Raises an exception if the element or the text is not found. The text only needs to be a subset within the complete text. + The text can be a string or a list/tuple of text substrings. Returns True if successful. Default timeout = SMALL_TIMEOUT.""" self.__check_scope() if not timeout: @@ -9686,26 +9514,45 @@ def assert_text( timeout = self.__get_new_timeout(timeout) original_selector = selector selector, by = self.__recalculate_selector(selector, by) - if self.__is_shadow_selector(selector): + if isinstance(text, (list, tuple)): + text_list = text + for _text in text_list: + self.wait_for_text_visible( + _text, selector, by=by, timeout=timeout + ) + if self.demo_mode: + a_t = "ASSERT TEXT" + i_n = "in" + if self._language != "English": + from seleniumbase.fixtures.words import SD + + a_t = SD.translate_assert_text(self._language) + i_n = SD.translate_in(self._language) + messenger_post = "%s: {%s} %s %s: %s" % ( + a_t, _text, i_n, by.upper(), selector + ) + self.__highlight_with_assert_success( + messenger_post, selector, by + ) + elif self.__is_shadow_selector(selector): self.__assert_shadow_text_visible(text, selector, timeout) return True - self.wait_for_text_visible(text, selector, by=by, timeout=timeout) - if self.demo_mode: - a_t = "ASSERT TEXT" - i_n = "in" - if self._language != "English": - from seleniumbase.fixtures.words import SD + else: + self.wait_for_text_visible(text, selector, by=by, timeout=timeout) + if self.demo_mode: + a_t = "ASSERT TEXT" + i_n = "in" + if self._language != "English": + from seleniumbase.fixtures.words import SD - a_t = SD.translate_assert_text(self._language) - i_n = SD.translate_in(self._language) - messenger_post = "%s: {%s} %s %s: %s" % ( - a_t, - text, - i_n, - by.upper(), - selector, - ) - self.__highlight_with_assert_success(messenger_post, selector, by) + a_t = SD.translate_assert_text(self._language) + i_n = SD.translate_in(self._language) + messenger_post = "%s: {%s} %s %s: %s" % ( + a_t, text, i_n, by.upper(), selector + ) + self.__highlight_with_assert_success( + messenger_post, selector, by + ) if self.recorder_mode and self.__current_url_is_recordable(): if self.get_session_storage_item("pause_recorder") == "no": if by == By.XPATH: @@ -9747,11 +9594,7 @@ def assert_exact_text( a_t = SD.translate_assert_exact_text(self._language) i_n = SD.translate_in(self._language) messenger_post = "%s: {%s} %s %s: %s" % ( - a_t, - text, - i_n, - by.upper(), - selector, + a_t, text, i_n, by.upper(), selector ) self.__highlight_with_assert_success(messenger_post, selector, by) if self.recorder_mode and self.__current_url_is_recordable(): @@ -9791,10 +9634,7 @@ def assert_non_empty_text( a_t = SD.translate_assert_non_empty_text(self._language) i_n = SD.translate_in(self._language) messenger_post = "%s %s %s: %s" % ( - a_t, - i_n, - by.upper(), - selector, + a_t, i_n, by.upper(), selector ) self.__highlight_with_assert_success(messenger_post, selector, by) if self.recorder_mode and self.__current_url_is_recordable(): @@ -9889,7 +9729,7 @@ def find_link_text(self, link_text, timeout=None): def assert_link_text(self, link_text, timeout=None): """Similar to wait_for_link_text_visible(), but returns nothing. - As above, will raise an exception if nothing can be found. + As above, raises an exception if nothing can be found. Returns True if successful. Default timeout = SMALL_TIMEOUT.""" self.__check_scope() if not timeout: @@ -9938,7 +9778,7 @@ def find_partial_link_text(self, partial_link_text, timeout=None): def assert_partial_link_text(self, partial_link_text, timeout=None): """Similar to wait_for_partial_link_text(), but returns nothing. - As above, will raise an exception if nothing can be found. + As above, raises an exception if nothing can be found. Returns True if successful. Default timeout = SMALL_TIMEOUT.""" self.__check_scope() if not timeout: @@ -9984,7 +9824,7 @@ def wait_for_element_absent( def assert_element_absent(self, selector, by="css selector", timeout=None): """Similar to wait_for_element_absent() - As above, will raise an exception if the element stays present. + As above, raises an exception if the element stays present. A hidden element counts as a present element, which fails this assert. If you want to assert that elements are hidden instead of nonexistent, use assert_element_not_visible() instead. @@ -10025,7 +9865,7 @@ def assert_element_not_visible( self, selector, by="css selector", timeout=None ): """Similar to wait_for_element_not_visible() - As above, will raise an exception if the element stays visible. + As above, raises an exception if the element stays visible. Returns True if successful. Default timeout = SMALL_TIMEOUT.""" self.__check_scope() if not timeout: @@ -10341,7 +10181,7 @@ def __assert_eq(self, *args, **kwargs): countdown_on = True countdown = 3 minified_exception += line + "\n" - elif line.startswith("+") or line.startswith("-"): + elif line.startswith(("+", "-")): minified_exception += line + "\n" elif line.startswith("?"): minified_exception += line + "\n" @@ -10390,8 +10230,6 @@ def __process_visual_baseline_logs(self): shutil.copy(latest_png_path, latest_copy_path) if len(self.__visual_baseline_copies) != 1: return # Skip the rest when deferred visual asserts are used - from seleniumbase.core import visual_helper - the_html = visual_helper.get_sbs_html() file_path = os.path.join(test_logpath, constants.SideBySide.HTML_FILE) out_file = codecs.open(file_path, "w+", encoding="utf-8") @@ -10478,12 +10316,10 @@ def check_window( self.check_window(name="github_page", level=2) self.check_window(name="wikipedia_page", level=3) """ self.wait_for_ready_state_complete() - try: + with suppress(Exception): self.wait_for_element_visible( "body", timeout=settings.MINI_TIMEOUT ) - except Exception: - pass if self.__needs_minimum_wait(): time.sleep(0.08) if level == "0": @@ -10509,7 +10345,6 @@ def check_window( if not name or len(name) < 1: name = "default" name = str(name) - from seleniumbase.core import visual_helper visual_helper.visual_baseline_folder_setup() baseline_dir = constants.VisualBaseline.STORAGE_FOLDER @@ -10792,14 +10627,12 @@ def deferred_assert_element( if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT: timeout = self.__get_new_timeout(timeout) self.__deferred_assert_count += 1 - try: + with suppress(Exception): url = self.get_current_url() if url == self.__last_url_of_deferred_assert: timeout = 0.6 # Was already on page (full wait not needed) else: self.__last_url_of_deferred_assert = 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();") @@ -10829,14 +10662,12 @@ def deferred_assert_element_present( if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT: timeout = self.__get_new_timeout(timeout) self.__deferred_assert_count += 1 - try: + with suppress(Exception): url = self.get_current_url() if url == self.__last_url_of_deferred_assert: timeout = 0.6 # Was already on page (full wait not needed) else: self.__last_url_of_deferred_assert = 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();") @@ -10866,14 +10697,12 @@ def deferred_assert_text( if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT: timeout = self.__get_new_timeout(timeout) self.__deferred_assert_count += 1 - try: + with suppress(Exception): url = self.get_current_url() if url == self.__last_url_of_deferred_assert: timeout = 0.6 # Was already on page (full wait not needed) else: self.__last_url_of_deferred_assert = 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();") @@ -10904,14 +10733,12 @@ def deferred_assert_exact_text( if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT: timeout = self.__get_new_timeout(timeout) self.__deferred_assert_count += 1 - try: + with suppress(Exception): url = self.get_current_url() if url == self.__last_url_of_deferred_assert: timeout = 0.6 # Was already on page (full wait not needed) else: self.__last_url_of_deferred_assert = 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();") @@ -10948,14 +10775,12 @@ def deferred_assert_non_empty_text( if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT: timeout = self.__get_new_timeout(timeout) self.__deferred_assert_count += 1 - try: + with suppress(Exception): url = self.get_current_url() if url == self.__last_url_of_deferred_assert: timeout = 0.6 # Was already on page (full wait not needed) else: self.__last_url_of_deferred_assert = 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();") @@ -11391,10 +11216,8 @@ def save_presentation( if saved_presentations_folder.endswith("/"): saved_presentations_folder = saved_presentations_folder[:-1] if not os.path.exists(saved_presentations_folder): - try: + with suppress(Exception): os.makedirs(saved_presentations_folder) - except Exception: - pass file_path = os.path.join(saved_presentations_folder, filename) out_file = codecs.open(file_path, "w+", encoding="utf-8") out_file.writelines(the_html) @@ -11448,7 +11271,7 @@ def begin_presentation( self._presentation_slides[name].pop() self.open_html_file(file_path) presentation_folder = constants.Presentations.SAVED_FOLDER - try: + with suppress(Exception): while ( len(self.driver.window_handles) > 0 and presentation_folder in self.get_current_url() @@ -11459,8 +11282,6 @@ def begin_presentation( ): break time.sleep(0.05) - except Exception: - pass ############ @@ -12090,10 +11911,8 @@ def save_chart(self, chart_name=None, filename=None, folder=None): if saved_charts_folder.endswith("/"): saved_charts_folder = saved_charts_folder[:-1] if not os.path.exists(saved_charts_folder): - try: + with suppress(Exception): os.makedirs(saved_charts_folder) - except Exception: - pass file_path = os.path.join(saved_charts_folder, filename) out_file = codecs.open(file_path, "w+", encoding="utf-8") out_file.writelines(the_html) @@ -12133,17 +11952,15 @@ def display_chart(self, chart_name=None, filename=None, interval=0): self.open_html_file(file_path) chart_folder = constants.Charts.SAVED_FOLDER if interval == 0: - try: + with suppress(Exception): print("\n*** Close the browser window to continue ***") # Will also continue if manually navigating to a new page while len(self.driver.window_handles) > 0 and ( chart_folder in self.get_current_url() ): time.sleep(0.05) - except Exception: - pass else: - try: + with suppress(Exception): start_ms = time.time() * 1000.0 stop_ms = start_ms + (interval * 1000.0) for x in range(int(interval * 10)): @@ -12155,8 +11972,6 @@ def display_chart(self, chart_name=None, filename=None, interval=0): if chart_folder not in self.get_current_url(): break time.sleep(0.1) - except Exception: - pass def extract_chart(self, chart_name=None): """Extracts the HTML from a SeleniumBase-generated chart. @@ -12968,10 +12783,8 @@ def get_jqc_button_input(self, message, buttons, options=None): ) time.sleep(0.02) jf = "document.querySelector('.jconfirm-box').focus();" - try: + with suppress(Exception): self.execute_script(jf) - except Exception: - pass waiting_for_response = True while waiting_for_response: time.sleep(0.05) @@ -13043,10 +12856,8 @@ def get_jqc_text_input(self, message, button=None, options=None): ) time.sleep(0.02) jf = "document.querySelector('.jconfirm-box input.jqc_input').focus();" - try: + with suppress(Exception): self.execute_script(jf) - except Exception: - pass waiting_for_response = True while waiting_for_response: time.sleep(0.05) @@ -13107,10 +12918,8 @@ def get_jqc_form_inputs(self, message, buttons, options=None): ) time.sleep(0.02) jf = "document.querySelector('.jconfirm-box input.jqc_input').focus();" - try: + with suppress(Exception): self.execute_script(jf) - except Exception: - pass waiting_for_response = True while waiting_for_response: time.sleep(0.05) @@ -13185,12 +12994,10 @@ def __js_click(self, selector, by="css selector"): if not page_actions.is_element_clickable( self.driver, selector, by ): - try: + with suppress(Exception): self.wait_for_element_clickable( selector, by, timeout=1.8 ) - except Exception: - pass # If the regular mouse-simulated click fails, do a basic JS click script = ( """document.querySelector('%s').click();""" @@ -13266,8 +13073,6 @@ def __click_with_offset( timeout=None, center=None, ): - from selenium.webdriver.common.action_chains import ActionChains - self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): time.sleep(0.14) @@ -13464,7 +13269,7 @@ def __click_dropdown_link_text(self, link_text, link_css): for dropdown in matching_dropdowns: # The same class names might be used for multiple dropdowns if dropdown.is_displayed(): - try: + with suppress(Exception): try: page_actions.hover_element( self.driver, @@ -13485,9 +13290,6 @@ def __click_dropdown_link_text(self, link_text, link_css): timeout=0.12, ) return True - except Exception: - pass - return False def __get_href_from_partial_link_text(self, link_text, hard_fail=True): @@ -13528,7 +13330,7 @@ def __click_dropdown_partial_link_text(self, link_text, link_css): for dropdown in matching_dropdowns: # The same class names might be used for multiple dropdowns if dropdown.is_displayed(): - try: + with suppress(Exception): try: page_actions.hover_element( self.driver, dropdown @@ -13550,8 +13352,6 @@ def __click_dropdown_partial_link_text(self, link_text, link_css): timeout=0.12, ) return True - except Exception: - pass return False def __recalculate_selector(self, selector, by, xp_ok=True): @@ -13778,7 +13578,7 @@ def __activate_standard_virtual_display(self): from sbvirtualdisplay import Display width = settings.HEADLESS_START_WIDTH height = settings.HEADLESS_START_HEIGHT - try: + with suppress(Exception): self._xvfb_display = Display( visible=0, size=(width, height) ) @@ -13786,11 +13586,9 @@ def __activate_standard_virtual_display(self): sb_config._virtual_display = self._xvfb_display self.headless_active = True sb_config.headless_active = True - except Exception: - pass def __activate_virtual_display_as_needed(self): - """Should be needed only on Linux. + """This is only needed on Linux. The "--xvfb" arg is still useful, as it prevents headless mode, which is the default mode on Linux unless using another arg.""" if "linux" in sys.platform and (not self.headed or self.xvfb): @@ -13802,9 +13600,13 @@ def __activate_virtual_display_as_needed(self): if self.undetectable and not (self.headless or self.headless2): import Xlib.display try: + if not self._xvfb_width: + self._xvfb_width = 1366 + if not self._xvfb_height: + self._xvfb_height = 768 self._xvfb_display = Display( visible=True, - size=(1366, 768), + size=(self._xvfb_width, self._xvfb_height), backend="xvfb", use_xauth=True, ) @@ -13825,7 +13627,7 @@ def __activate_virtual_display_as_needed(self): pyautogui_is_installed = False try: import pyautogui - try: + with suppress(Exception): use_pyautogui_ver = constants.PyAutoGUI.VER if pyautogui.__version__ != use_pyautogui_ver: del pyautogui # To get newer ver @@ -13833,8 +13635,6 @@ def __activate_virtual_display_as_needed(self): "pyautogui", version=use_pyautogui_ver ) import pyautogui - except Exception: - pass pyautogui_is_installed = True except Exception: message = ( @@ -14086,12 +13886,10 @@ def __shadow_type(self, selector, text, timeout, clear_first=True): selector, timeout=timeout, must_be_visible=True ) if clear_first: - try: + with suppress(Exception): element.clear() backspaces = Keys.BACK_SPACE * 42 # Autofill Defense element.send_keys(backspaces) - except Exception: - pass text = self.__get_type_checked_text(text) if not text.endswith("\n"): element.send_keys(text) @@ -14107,12 +13905,10 @@ def __shadow_clear(self, selector, timeout): element = self.__get_shadow_element( selector, timeout=timeout, must_be_visible=True ) - try: + with suppress(Exception): element.clear() backspaces = Keys.BACK_SPACE * 42 # Autofill Defense element.send_keys(backspaces) - except Exception: - pass def __get_shadow_text(self, selector, timeout): element = self.__get_shadow_element( @@ -14232,19 +14028,13 @@ def __assert_shadow_text_visible(self, text, selector, timeout): a_t = SD.translate_assert_text(self._language) i_n = SD.translate_in(self._language) messenger_post = "%s: {%s} %s %s: %s" % ( - a_t, - text, - i_n, - by.upper(), - selector, + a_t, text, i_n, by.upper(), selector ) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def __assert_exact_shadow_text_visible(self, text, selector, timeout): self.__wait_for_exact_shadow_text_visible(text, selector, timeout) @@ -14258,19 +14048,13 @@ def __assert_exact_shadow_text_visible(self, text, selector, timeout): a_t = SD.translate_assert_exact_text(self._language) i_n = SD.translate_in(self._language) messenger_post = "%s: {%s} %s %s: %s" % ( - a_t, - text, - i_n, - by.upper(), - selector, + a_t, text, i_n, by.upper(), selector ) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def __assert_non_empty_shadow_text_visible(self, selector, timeout, strip): self.__wait_for_non_empty_shadow_text_visible(selector, timeout, strip) @@ -14284,18 +14068,13 @@ def __assert_non_empty_shadow_text_visible(self, selector, timeout, strip): a_t = SD.translate_assert_non_empty_text(self._language) i_n = SD.translate_in(self._language) messenger_post = "%s %s %s: %s" % ( - a_t, - i_n, - by.upper(), - selector, + a_t, i_n, by.upper(), selector ) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def __is_shadow_element_present(self, selector): try: @@ -14447,13 +14226,11 @@ def __assert_shadow_element_present(self, selector): a_t = SD.translate_assert(self._language) messenger_post = "%s %s: %s" % (a_t, by.upper(), selector) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass def __assert_shadow_element_visible(self, selector): element = self.__get_shadow_element(selector) @@ -14468,13 +14245,11 @@ def __assert_shadow_element_visible(self, selector): a_t = SD.translate_assert(self._language) messenger_post = "%s %s: %s" % (a_t, by.upper(), selector) - try: + with suppress(Exception): js_utils.activate_jquery(self.driver) js_utils.post_messenger_success_message( self.driver, messenger_post, self.message_duration ) - except Exception: - pass ############ @@ -14553,6 +14328,7 @@ def setUp(self, masterqa_mode=False): sb_config.headless_active = False self.headed = sb_config.headed self.xvfb = sb_config.xvfb + self.xvfb_metrics = sb_config.xvfb_metrics self.locale_code = sb_config.locale_code self.interval = sb_config.interval self.start_page = sb_config.start_page @@ -14644,31 +14420,8 @@ def setUp(self, masterqa_mode=False): self.use_wire = sb_config.use_wire self.external_pdf = sb_config.external_pdf self._final_debug = sb_config.final_debug + self.window_position = sb_config.window_position self.window_size = sb_config.window_size - window_size = self.window_size - if window_size: - if window_size.count(",") != 1: - message = ( - '\n\n window_size expects a "width,height" string!' - '\n (Your input was: "%s")\n' % window_size - ) - raise Exception(message) - window_size = window_size.replace(" ", "") - width = None - height = None - try: - width = int(window_size.split(",")[0]) - height = int(window_size.split(",")[1]) - except Exception: - message = ( - '\n\n Expecting integer values for "width,height"!' - '\n (window_size input was: "%s")\n' % window_size - ) - raise Exception(message) - settings.CHROME_START_WIDTH = width - settings.CHROME_START_HEIGHT = height - settings.HEADLESS_START_WIDTH = width - settings.HEADLESS_START_HEIGHT = height self.maximize_option = sb_config.maximize_option self.save_screenshot_after_test = sb_config.save_screenshot self.no_screenshot_after_test = sb_config.no_screenshot @@ -14832,9 +14585,87 @@ def setUp(self, masterqa_mode=False): self.mobile_emulator = True except Exception: raise Exception(exception_string) + + window_position = self.window_position + if window_position: + if window_position.count(",") != 1: + message = ( + '\n\n window_position expects an "x,y" string!' + '\n (Your input was: "%s")\n' % window_position + ) + raise Exception(message) + window_position = window_position.replace(" ", "") + win_x = None + win_y = None + try: + win_x = int(window_position.split(",")[0]) + win_y = int(window_position.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "x,y"!' + '\n (window_position input was: "%s")\n' + % window_position + ) + raise Exception(message) + settings.WINDOW_START_X = win_x + settings.WINDOW_START_Y = win_y + + window_size = self.window_size + if window_size: + if window_size.count(",") != 1: + message = ( + '\n\n window_size expects a "width,height" string!' + '\n (Your input was: "%s")\n' % window_size + ) + raise Exception(message) + window_size = window_size.replace(" ", "") + width = None + height = None + try: + width = int(window_size.split(",")[0]) + height = int(window_size.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "width,height"!' + '\n (window_size input was: "%s")\n' % window_size + ) + raise Exception(message) + settings.CHROME_START_WIDTH = width + settings.CHROME_START_HEIGHT = height + settings.HEADLESS_START_WIDTH = width + settings.HEADLESS_START_HEIGHT = height + + if self.xvfb_metrics: + metrics_string = self.xvfb_metrics + metrics_string = metrics_string.replace(" ", "") + metrics_list = metrics_string.split(",")[0:2] + exception_string = ( + "Invalid input for xvfb_metrics!\n" + "Expecting a comma-separated string\n" + "with integer values for Width/Height.\n" + 'Eg. --xvfb-metrics="1920,1080".\n' + "(Minimum: 1024,768) (Default: 1366,768)" + ) + if len(metrics_list) != 2: + raise Exception(exception_string) + try: + self._xvfb_width = int(metrics_list[0]) + self._xvfb_height = int(metrics_list[1]) + # The minimum width,height is: 1024,768 + if self._xvfb_width < 1024: + self._xvfb_width = 1024 + sb_config._xvfb_width = self._xvfb_width + if self._xvfb_height < 768: + self._xvfb_height = 768 + sb_config._xvfb_height = self._xvfb_height + self.xvfb = True + except Exception: + raise Exception(exception_string) + if self.mobile_emulator and not self.user_agent: # Use a Pixel user agent by default if not specified self.user_agent = constants.Mobile.AGENT + if self.browser in ["firefox", "ie", "safari"]: # The Recorder Mode browser extension is only for Chrome/Edge. if self.recorder_mode: @@ -14868,7 +14699,7 @@ def setUp(self, masterqa_mode=False): if not hasattr(sb_config, "shared_driver"): sb_config.shared_driver = None if sb_config.shared_driver: - try: + with suppress(Exception): self._default_driver = sb_config.shared_driver self.driver = sb_config.shared_driver self._drivers_list = [sb_config.shared_driver] @@ -14882,12 +14713,8 @@ def setUp(self, masterqa_mode=False): self.switch_to_window(0) if self._crumbs: self.wait_for_ready_state_complete() - try: + with suppress(Exception): self.driver.delete_all_cookies() - except Exception: - pass - except Exception: - pass if self._reuse_session and sb_config.shared_driver and has_url: good_start_page = False if self.recorder_ext: @@ -14974,10 +14801,8 @@ def setUp(self, masterqa_mode=False): if self.driver.timeouts.implicit_wait > 0: self.driver.implicitly_wait(0) except Exception: - try: + with suppress(Exception): self.driver.implicitly_wait(0) - except Exception: - pass self._default_driver = self.driver if self._reuse_session: sb_config.shared_driver = self.driver @@ -14996,13 +14821,11 @@ def setUp(self, masterqa_mode=False): self.set_time_limit(self.time_limit) # Configure the page load timeout - try: + with suppress(Exception): if hasattr(settings, "PAGE_LOAD_TIMEOUT"): self.driver.set_page_load_timeout(settings.PAGE_LOAD_TIMEOUT) else: self.driver.set_page_load_timeout(120) # Selenium uses 300 - except Exception: - pass # Set the start time for the test (in ms). # Although the pytest clock starts before setUp() begins, @@ -15031,7 +14854,7 @@ def __set_last_page_screenshot(self): not self.__last_page_screenshot and not self.__last_page_screenshot_png ): - try: + with suppress(Exception): try: element = page_actions.wait_for_element_visible( self.driver, @@ -15061,14 +14884,10 @@ def __set_last_page_screenshot(self): element.screenshot_as_base64 ) except Exception: - try: + with suppress(Exception): self.__last_page_screenshot = ( self.driver.get_screenshot_as_base64() ) - except Exception: - pass - except Exception: - pass if not self.__last_page_screenshot: self.__last_page_screenshot = SCREENSHOT_UNDEFINED self.__last_page_screenshot_png = SCREENSHOT_UNDEFINED @@ -15078,12 +14897,10 @@ def __set_last_page_screenshot(self): element.screenshot_as_png ) except Exception: - try: + with suppress(Exception): self.__last_page_screenshot_png = ( self.driver.get_screenshot_as_png() ) - except Exception: - pass else: import base64 @@ -15098,12 +14915,10 @@ def __set_last_page_screenshot(self): element.screenshot_as_png ) except Exception: - try: + with suppress(Exception): self.__last_page_screenshot_png = ( self.driver.get_screenshot_as_png() ) - except Exception: - pass sb_config._last_page_screenshot_png = self.__last_page_screenshot_png def __set_last_page_url(self): @@ -15155,7 +14970,6 @@ def __insert_test_result(self, state, err): data_payload.state = state if err: import traceback - tb_string = traceback.format_exc() if "Message: " in tb_string: data_payload.message = ( @@ -15190,7 +15004,7 @@ def _add_pytest_html_extra(self): def __add_pytest_html_extra(self): if not self.__added_pytest_html_extra: - try: + with suppress(Exception): if self.with_selenium: if not self.__last_page_screenshot: self.__set_last_page_screenshot() @@ -15217,8 +15031,6 @@ def __add_pytest_html_extra(self): ): self._html_report_extra.append(extra_url) self._html_report_extra.append(extra_image) - except Exception: - pass def __delay_driver_quit(self): delay_driver_quit = False @@ -15312,7 +15124,6 @@ def __get_test_id(self): context_id = None if filename == "base_case.py" or methodname == "runTest": import traceback - stack_base = traceback.format_stack()[0].split(", in ")[0] test_base = stack_base.split(", in ")[0].split(os.sep)[-1] if hasattr(self, "cm_filename") and self.cm_filename: @@ -16034,7 +15845,14 @@ def tearDown(self): self.driver.window_handles except Exception: self.driver.connect() - self.__process_recorded_actions() + try: + self.__process_recorded_actions() + except Exception as e: + print("\n (Recorder) Code-generation exception:") + if hasattr(e, "msg"): + print("\n" + str(e.msg)) + else: + print(e) self.__called_teardown = True self.__called_setup = False try: @@ -16071,10 +15889,8 @@ def tearDown(self): try: self.driver.window_handles except Exception: - try: + with suppress(Exception): self.driver.connect() - except Exception: - pass self.__slow_mode_pause_if_active() has_exception = self.__has_exception() sb_config._has_exception = has_exception @@ -16264,7 +16080,6 @@ def tearDown(self): else: # (Pynose / Behave / Pure Python) if hasattr(self, "is_behave") and self.is_behave: - import colorama if sb_config.behave_scenario.status.name == "failed": has_exception = True sb_config._has_exception = True diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index de7420a8bd1..ff85ed57f32 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -2,6 +2,7 @@ import re import requests import time +from contextlib import suppress from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By @@ -56,11 +57,9 @@ def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT): def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS: return - try: + with suppress(Exception): # This closes pop-up alerts driver.execute_script("") - except Exception: - pass if ( (hasattr(driver, "_is_using_uc") and driver._is_using_uc) or not settings.WAIT_FOR_ANGULARJS @@ -92,10 +91,8 @@ def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): "handler": handler, "suffix": suffix, } - try: + with suppress(Exception): execute_async_script(driver, script, timeout=timeout) - except Exception: - pass def convert_to_css_selector(selector, by=By.CSS_SELECTOR): @@ -191,14 +188,12 @@ def raise_unable_to_load_jquery_exception(driver): def activate_jquery(driver): # If "jQuery is not defined" on a website, use this method to activate it. # This method is needed because jQuery is not always defined on web sites. - try: + with suppress(Exception): # Let's first find out if jQuery is already defined. driver.execute_script("jQuery('html');") # Since that command worked, jQuery is defined. Let's return. return - except Exception: - # jQuery is not currently defined. Let's proceed by defining it. - pass + # jQuery is not defined. It will be loaded in the next part. jquery_js = constants.JQuery.MIN_JS add_js_link(driver, jquery_js) for x in range(36): @@ -390,11 +385,9 @@ def highlight(driver, selector, by="css selector", loops=4): def highlight_with_js(driver, selector, loops=4, o_bs=""): - try: + with suppress(Exception): # This closes any pop-up alerts driver.execute_script("") - except Exception: - pass if selector == "html": selector = "body" selector_no_spaces = selector.replace(" ", "") @@ -490,11 +483,9 @@ def highlight_with_js(driver, selector, loops=4, o_bs=""): def highlight_element_with_js(driver, element, loops=4, o_bs=""): - try: + with suppress(Exception): # This closes any pop-up alerts driver.execute_script("") - except Exception: - pass script = ( """arguments[0].style.boxShadow = '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';""" @@ -566,11 +557,9 @@ def highlight_element_with_js(driver, element, loops=4, o_bs=""): def highlight_with_jquery(driver, selector, loops=4, o_bs=""): - try: + with suppress(Exception): # This closes any pop-up alerts driver.execute_script("") - except Exception: - pass if selector == "html": selector = "body" selector_no_spaces = selector.replace(" ", "") @@ -908,11 +897,9 @@ def set_messenger_theme( time.sleep(0.03) activate_messenger(driver) time.sleep(0.15) - try: + with suppress(Exception): driver.execute_script(msg_style) time.sleep(0.02) - except Exception: - pass time.sleep(0.05) @@ -949,7 +936,7 @@ def post_messenger_success_message(driver, message, msg_dur=None): if not msg_dur: msg_dur = settings.DEFAULT_MESSAGE_DURATION msg_dur = float(msg_dur) - try: + with suppress(Exception): theme = "future" location = "bottom_right" if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator: @@ -957,28 +944,22 @@ def post_messenger_success_message(driver, message, msg_dur=None): set_messenger_theme(driver, theme=theme, location=location) post_message(driver, message, msg_dur, style="success") time.sleep(msg_dur + 0.07) - except Exception: - pass def post_messenger_error_message(driver, message, msg_dur=None): if not msg_dur: msg_dur = settings.DEFAULT_MESSAGE_DURATION msg_dur = float(msg_dur) - try: + with suppress(Exception): set_messenger_theme(driver, theme="block", location="top_center") post_message(driver, message, msg_dur, style="error") time.sleep(msg_dur + 0.07) - except Exception: - pass def highlight_with_js_2(driver, message, selector, o_bs, msg_dur): - try: + with suppress(Exception): # This closes any pop-up alerts driver.execute_script("") - except Exception: - pass if selector == "html": selector = "body" selector_no_spaces = selector.replace(" ", "") @@ -991,11 +972,9 @@ def highlight_with_js_2(driver, message, selector, o_bs, msg_dur): else: early_exit = True # Changing the box-shadow changes the selector if early_exit: - try: + with suppress(Exception): activate_jquery(driver) post_messenger_success_message(driver, message, msg_dur) - except Exception: - pass return script = ( """document.querySelector('%s').style.boxShadow = @@ -1047,11 +1026,9 @@ def highlight_with_js_2(driver, message, selector, o_bs, msg_dur): except Exception: return time.sleep(0.0181) - try: + with suppress(Exception): activate_jquery(driver) post_messenger_success_message(driver, message, msg_dur) - except Exception: - pass script = """document.querySelector('%s').style.boxShadow = '%s';""" % ( selector, o_bs, @@ -1063,11 +1040,9 @@ def highlight_with_js_2(driver, message, selector, o_bs, msg_dur): def highlight_element_with_js_2(driver, message, element, o_bs, msg_dur): - try: + with suppress(Exception): # This closes any pop-up alerts driver.execute_script("") - except Exception: - pass script = ( """arguments[0].style.boxShadow = '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';""" @@ -1113,11 +1088,9 @@ def highlight_element_with_js_2(driver, message, element, o_bs, msg_dur): except Exception: return time.sleep(0.0181) - try: + with suppress(Exception): activate_jquery(driver) post_messenger_success_message(driver, message, msg_dur) - except Exception: - pass script = """arguments[0].style.boxShadow = '%s';""" % (o_bs) try: driver.execute_script(script, element) @@ -1138,11 +1111,9 @@ def highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur): else: early_exit = True # Changing the box-shadow changes the selector if early_exit: - try: + with suppress(Exception): activate_jquery(driver) post_messenger_success_message(driver, message, msg_dur) - except Exception: - pass return script = ( """jQuery('%s').css('box-shadow', @@ -1195,11 +1166,9 @@ def highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur): return time.sleep(0.0181) - try: + with suppress(Exception): activate_jquery(driver) post_messenger_success_message(driver, message, msg_dur) - except Exception: - pass script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs) try: @@ -1484,12 +1453,10 @@ def get_drag_and_drop_with_offset_script(selector, x, y): def clear_out_console_logs(driver): - try: + with suppress(Exception): # Clear out the current page log before navigating to a new page # (To make sure that assert_no_js_errors() uses current results) driver.get_log("browser") - except Exception: - pass def _jq_format(code): diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index f6c657faaae..3e0b1506cd1 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -21,6 +21,7 @@ import fasteners import os import time +from contextlib import suppress from selenium.common.exceptions import ElementNotInteractableException from selenium.common.exceptions import ElementNotVisibleException from selenium.common.exceptions import NoAlertPresentException @@ -1409,12 +1410,10 @@ def switch_to_frame(driver, frame, timeout=settings.SMALL_TIMEOUT): else: by = "css selector" if is_element_visible(driver, frame, by=by): - try: + with suppress(Exception): element = driver.find_element(by=by, value=frame) driver.switch_to.frame(element) return True - except Exception: - pass now_ms = time.time() * 1000.0 if now_ms >= stop_ms: break @@ -1548,12 +1547,10 @@ def click_if_visible( if is_element_visible(driver, selector, by=by): click(driver, selector, by=by, timeout=1) elif timeout > 0: - try: + with suppress(Exception): wait_for_element_visible( driver, selector, by=by, timeout=timeout ) - except Exception: - pass if is_element_visible(driver, selector, by=by): click(driver, selector, by=by, timeout=1) diff --git a/seleniumbase/fixtures/page_utils.py b/seleniumbase/fixtures/page_utils.py index 18044a8b1d3..ae02add9c76 100644 --- a/seleniumbase/fixtures/page_utils.py +++ b/seleniumbase/fixtures/page_utils.py @@ -16,7 +16,7 @@ def get_domain_url(url): Into this: https://blog.xkcd.com """ - if not url.startswith("http://") and not url.startswith("https://"): + if not url.startswith(("http://", "https://")): return url url_header = url.split("://")[0] simple_url = url.split("://")[1] @@ -40,45 +40,25 @@ def swap_selector_and_by_if_reversed(selector, by): def is_xpath_selector(selector): """Determine if a selector is an xpath selector.""" - if ( - selector.startswith("/") - or selector.startswith("./") - or selector.startswith("(") - ): - return True - return False + return selector.startswith(("/", "./", "(")) def is_link_text_selector(selector): """Determine if a selector is a link text selector.""" - if ( - selector.startswith("link=") - or selector.startswith("link_text=") - or selector.startswith("text=") - ): - return True - return False + return selector.startswith(("link=", "link_text=", "text=")) def is_partial_link_text_selector(selector): """Determine if a selector is a partial link text selector.""" - if ( - selector.startswith("partial_link=") - or selector.startswith("partial_link_text=") - or selector.startswith("partial_text=") - or selector.startswith("p_link=") - or selector.startswith("p_link_text=") - or selector.startswith("p_text=") - ): - return True - return False + return selector.startswith(( + "partial_link=", "partial_link_text=", "partial_text=", + "p_link=", "p_link_text=", "p_text=" + )) def is_name_selector(selector): """Determine if a selector is a name selector.""" - if selector.startswith("name=") or selector.startswith("&"): - return True - return False + return selector.startswith(("name=", "&")) def recalculate_selector(selector, by, xp_ok=True): @@ -108,10 +88,9 @@ def recalculate_selector(selector, by, xp_ok=True): name = get_name_from_selector(selector) selector = '[name="%s"]' % name by = By.CSS_SELECTOR - if xp_ok: - if ":contains(" in selector and by == By.CSS_SELECTOR: - selector = css_to_xpath.convert_css_to_xpath(selector) - by = By.XPATH + if xp_ok and ":contains(" in selector and by == By.CSS_SELECTOR: + selector = css_to_xpath.convert_css_to_xpath(selector) + by = By.XPATH if by == "": by = By.CSS_SELECTOR if not is_valid_by(by): @@ -130,21 +109,10 @@ def looks_like_a_page_url(url): possible typos when calling self.get(url), which will try to navigate to the page if a URL is detected, but will instead call self.get_element(URL_AS_A_SELECTOR) if the input is not a URL.""" - if ( - url.startswith("http:") - or url.startswith("https:") - or url.startswith("://") - or url.startswith("about:") - or url.startswith("blob:") - or url.startswith("chrome:") - or url.startswith("data:") - or url.startswith("edge:") - or url.startswith("file:") - or url.startswith("view-source:") - ): - return True - else: - return False + return url.startswith(( + "http:", "https:", "://", "about:", "blob:", "chrome:", + "data:", "edge:", "file:", "view-source:" + )) def get_link_text_from_selector(selector): @@ -195,18 +163,12 @@ def is_valid_url(url): r"(?:/?|[/?]\S+)$", re.IGNORECASE, ) - if ( + return ( regex.match(url) - or url.startswith("about:") - or url.startswith("blob:") - or url.startswith("chrome:") - or url.startswith("data:") - or url.startswith("edge:") - or url.startswith("file:") - ): - return True - else: - return False + or url.startswith(( + "about:", "blob:", "chrome:", "data:", "edge:", "file:" + )) + ) def _get_unique_links(page_url, soup): diff --git a/seleniumbase/plugins/base_plugin.py b/seleniumbase/plugins/base_plugin.py index 61b6da01afe..2c4f945a2cd 100644 --- a/seleniumbase/plugins/base_plugin.py +++ b/seleniumbase/plugins/base_plugin.py @@ -2,6 +2,7 @@ import ast import sys import time +from contextlib import suppress from nose.plugins import Plugin from seleniumbase import config as sb_config from seleniumbase.config import settings @@ -305,14 +306,12 @@ def add_fails_or_errors(self, test, err): if python3_11_or_newer and py311_patch2: # Handle a bug on Python 3.11 where exceptions aren't seen sb_config._browser_version = None - try: + with suppress(Exception): test._BaseCase__set_last_page_screenshot() test._BaseCase__set_last_page_url() test._BaseCase__set_last_page_source() sb_config._browser_version = test._get_browser_version() test._log_fail_data() - except Exception: - pass sb_config._excinfo_tb = err log_path = None if hasattr(sb_config, "_test_logpath"): diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 6c292433e5e..a3f19e5a48e 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -7,7 +7,7 @@ Example --> -``` +```python from seleniumbase import DriverContext with DriverContext() as driver: @@ -27,7 +27,7 @@ Example --> -``` +```python from seleniumbase import Driver driver = Driver() @@ -75,7 +75,7 @@ def Driver( proxy=None, # Use proxy. Format: "SERVER:PORT" or "USER:PASS@SERVER:PORT". proxy_bypass_list=None, # Skip proxy when using the listed domains. proxy_pac_url=None, # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL) - multi_proxy=False, # Allow multiple proxies with auth when multi-threaded. + multi_proxy=None, # Allow multiple proxies with auth when multi-threaded. agent=None, # Modify the web browser's User-Agent string. cap_file=None, # The desired capabilities to use with a Selenium Grid. cap_string=None, # The desired capabilities to use with a Selenium Grid. @@ -115,6 +115,8 @@ def Driver( page_load_strategy=None, # Set Chrome PLS to "normal", "eager", or "none". use_wire=None, # Use selenium-wire's webdriver over selenium webdriver. external_pdf=None, # Set Chrome "plugins.always_open_pdf_externally":True. + window_position=None, # Set the browser's starting window position: "X,Y" + window_size=None, # Set the browser's starting window size: "Width,Height" is_mobile=None, # Use the mobile device emulator while running tests. mobile=None, # Shortcut / Duplicate of "is_mobile". d_width=None, # Set device width @@ -131,7 +133,98 @@ def Driver( wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". ): + """ + * SeleniumBase Driver as a Python Context Manager or a returnable object. * + + Example 1: (context manager format) + ----------------------------------- + .. code-block:: python + from seleniumbase import DriverContext + + with DriverContext() as driver: + driver.get("https://google.com/ncr") + + Example 2: (as a Python returnable) + ----------------------------------- + .. code-block:: python + from seleniumbase import Driver + + driver = Driver() + driver.get("https://google.com/ncr") + + Optional Parameters: + -------------------- + browser: # Choose from "chrome", "edge", "firefox", or "safari". + headless: # The original headless mode for Chromium and Firefox. + headless2: # Chromium's new headless mode. (Has more features) + headed: # Run tests in headed/GUI mode on Linux, where not default. + locale_code: # Set the Language Locale Code for the web browser. + protocol: # The Selenium Grid protocol: "http" or "https". + servername: # The Selenium Grid server/IP used for tests. + port: # The Selenium Grid port used by the test server. + proxy: # Use proxy. Format: "SERVER:PORT" or "USER:PASS@SERVER:PORT". + proxy_bypass_list: # Skip proxy when using the listed domains. + proxy_pac_url: # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL) + multi_proxy: # Allow multiple proxies with auth when multi-threaded. + agent: # Modify the web browser's User-Agent string. + cap_file: # The desired capabilities to use with a Selenium Grid. + cap_string: # The desired capabilities to use with a Selenium Grid. + recorder_ext: # Enables the SeleniumBase Recorder Chromium extension. + disable_js: # Disable JavaScript on websites. Pages might break! + disable_csp: # Disable the Content Security Policy of websites. + enable_ws: # Enable Web Security on Chromium-based browsers. + disable_ws: # Reverse of "enable_ws". (None and False are different) + enable_sync: # Enable "Chrome Sync" on websites. + use_auto_ext: # Use Chrome's automation extension. + undetectable: # Use undetected-chromedriver to evade bot-detection. + uc_cdp_events: # Capture CDP events in undetected-chromedriver mode. + uc_subprocess: # Use undetected-chromedriver as a subprocess. + log_cdp_events: # Capture {"performance": "ALL", "browser": "ALL"} + no_sandbox: # (DEPRECATED) - "--no-sandbox" is always used now. + disable_gpu: # (DEPRECATED) - GPU is disabled if not "swiftshader". + incognito: # Enable Chromium's Incognito mode. + guest_mode: # Enable Chromium's Guest mode. + dark_mode: # Enable Chromium's Dark mode. + devtools: # Open Chromium's DevTools when the browser opens. + remote_debug: # Enable Chrome's Debugger on "http://localhost:9222". + enable_3d_apis: # Enable WebGL and 3D APIs. + swiftshader: # Chrome: --use-gl=angle / --use-angle=swiftshader-webgl + ad_block_on: # Block some types of display ads from loading. + host_resolver_rules: # Set host-resolver-rules, comma-separated. + block_images: # Block images from loading during tests. + do_not_track: # Tell websites that you don't want to be tracked. + chromium_arg: # "ARG=N,ARG2" (Set Chromium args, ","-separated.) + firefox_arg: # "ARG=N,ARG2" (Set Firefox args, comma-separated.) + firefox_pref: # SET (Set Firefox PREFERENCE:VALUE set, ","-separated) + user_data_dir: # Set the Chrome user data directory to use. + extension_zip: # Load a Chrome Extension .zip|.crx, comma-separated. + extension_dir: # Load a Chrome Extension directory, comma-separated. + disable_features: # "F1,F2" (Disable Chrome features, ","-separated.) + binary_location: # Set path of the Chromium browser binary to use. + driver_version: # Set the chromedriver or uc_driver version to use. + page_load_strategy: # Set Chrome PLS to "normal", "eager", or "none". + use_wire: # Use selenium-wire's webdriver over selenium webdriver. + external_pdf: # Set Chrome "plugins.always_open_pdf_externally":True. + window_position: # Set the browser's starting window position: "X,Y" + window_size: # Set the browser's starting window size: "Width,Height" + is_mobile: # Use the mobile device emulator while running tests. + mobile: # Shortcut / Duplicate of "is_mobile". + d_width: # Set device width + d_height: # Set device height + d_p_r: # Set device pixel ratio + uc: # Shortcut / Duplicate of "undetectable". + undetected: # Shortcut / Duplicate of "undetectable". + uc_cdp: # Shortcut / Duplicate of "uc_cdp_events". + uc_sub: # Shortcut / Duplicate of "uc_subprocess". + log_cdp: # Shortcut / Duplicate of "log_cdp_events". + ad_block: # Shortcut / Duplicate of "ad_block_on". + server: # Shortcut / Duplicate of "servername". + guest: # Shortcut / Duplicate of "guest_mode". + wire: # Shortcut / Duplicate of "use_wire". + pls: # Shortcut / Duplicate of "page_load_strategy". + """ from seleniumbase import config as sb_config + from seleniumbase.config import settings from seleniumbase.fixtures import constants from seleniumbase.fixtures import shared_utils @@ -329,6 +422,79 @@ def Driver( break count += 1 disable_features = d_f + w_p = window_position + if w_p is None and "--window-position" in arg_join: + count = 0 + for arg in sys_argv: + if arg.startswith("--window-position="): + w_p = arg.split("--window-position=")[1] + break + elif arg == "--window-position" and len(sys_argv) > count + 1: + w_p = sys_argv[count + 1] + if w_p.startswith("-"): + w_p = None + break + count += 1 + window_position = w_p + if window_position: + if window_position.count(",") != 1: + message = ( + '\n\n window_position expects an "x,y" string!' + '\n (Your input was: "%s")\n' % window_position + ) + raise Exception(message) + window_position = window_position.replace(" ", "") + win_x = None + win_y = None + try: + win_x = int(window_position.split(",")[0]) + win_y = int(window_position.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "x,y"!' + '\n (window_position input was: "%s")\n' + % window_position + ) + raise Exception(message) + settings.WINDOW_START_X = win_x + settings.WINDOW_START_Y = win_y + w_s = window_size + if w_s is None and "--window-size" in arg_join: + count = 0 + for arg in sys_argv: + if arg.startswith("--window-size="): + w_s = arg.split("--window-size=")[1] + break + elif arg == "--window-size" and len(sys_argv) > count + 1: + w_s = sys_argv[count + 1] + if w_s.startswith("-"): + w_s = None + break + count += 1 + window_size = w_s + if window_size: + if window_size.count(",") != 1: + message = ( + '\n\n window_size expects a "width,height" string!' + '\n (Your input was: "%s")\n' % window_size + ) + raise Exception(message) + window_size = window_size.replace(" ", "") + width = None + height = None + try: + width = int(window_size.split(",")[0]) + height = int(window_size.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "width,height"!' + '\n (window_size input was: "%s")\n' % window_size + ) + raise Exception(message) + settings.CHROME_START_WIDTH = width + settings.CHROME_START_HEIGHT = height + settings.HEADLESS_START_WIDTH = width + settings.HEADLESS_START_HEIGHT = height if agent is None and "--agent" in arg_join: count = 0 for arg in sys_argv: diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 9a5efe76d86..16de9d68010 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -4,6 +4,7 @@ import pytest import sys import time +from contextlib import suppress from seleniumbase import config as sb_config from seleniumbase.config import settings from seleniumbase.core import log_helper @@ -65,6 +66,7 @@ def pytest_addoption(parser): --headless2 (Use the new headless mode, which supports extensions.) --headed (Run tests in headed/GUI mode on Linux OS, where not default.) --xvfb (Run tests using the Xvfb virtual display server on Linux OS.) + --xvfb-metrics=STRING (Set Xvfb display size on Linux: "Width,Height".) --locale=LOCALE_CODE (Set the Language Locale Code for the web browser.) --interval=SECONDS (The autoplay interval for presentations & tour steps) --start-page=URL (The starting URL for the web browser when tests begin.) @@ -109,6 +111,7 @@ def pytest_addoption(parser): --rcs | --reuse-class-session (Reuse session for tests in class.) --crumbs (Delete all cookies between tests reusing a session.) --disable-beforeunload (Disable the "beforeunload" event on Chrome.) + --window-position=X,Y (Set the browser's starting window position.) --window-size=WIDTH,HEIGHT (Set the browser's starting window size.) --maximize (Start tests with the browser window maximized.) --screenshot (Save a screenshot at the end of each test.) @@ -728,6 +731,17 @@ def pytest_addoption(parser): will no longer be enabled by default on Linux. Default: False. (Linux-ONLY!)""", ) + parser.addoption( + "--xvfb-metrics", + "--xvfb_metrics", + action="store", + dest="xvfb_metrics", + default=None, + help="""Customize the Xvfb metrics (Width,Height) on Linux. + Format: A comma-separated string with the 2 values. + Examples: "1920,1080" or "1366,768" or "1024,768". + Default: None. (None: "1366,768". Min: "1024,768".)""", + ) parser.addoption( "--locale_code", "--locale-code", @@ -1229,6 +1243,17 @@ def pytest_addoption(parser): on Chromium browsers (Chrome or Edge). This is already the default Firefox option.""", ) + parser.addoption( + "--window-position", + "--window_position", + action="store", + dest="window_position", + default=None, + help="""The option to set the starting window x,y position + Format: A comma-separated string with the 2 values. + Example: "55,66" + Default: None. (Will use default values if None)""", + ) parser.addoption( "--window-size", "--window_size", @@ -1516,6 +1541,7 @@ def pytest_configure(config): sb_config.headless2 = False # Only for Chromium browsers sb_config.headed = config.getoption("headed") sb_config.xvfb = config.getoption("xvfb") + sb_config.xvfb_metrics = config.getoption("xvfb_metrics") sb_config.locale_code = config.getoption("locale_code") sb_config.interval = config.getoption("interval") sb_config.start_page = config.getoption("start_page") @@ -1624,6 +1650,7 @@ def pytest_configure(config): sb_config.shared_driver = None # The default driver for session reuse sb_config.crumbs = config.getoption("crumbs") sb_config._disable_beforeunload = config.getoption("_disable_beforeunload") + sb_config.window_position = config.getoption("window_position") sb_config.window_size = config.getoption("window_size") sb_config.maximize_option = config.getoption("maximize_option") sb_config.save_screenshot = config.getoption("save_screenshot") @@ -1822,10 +1849,8 @@ def _create_dashboard_assets_(): abs_path = os.path.abspath(".") assets_folder = os.path.join(abs_path, "assets") if not os.path.exists(assets_folder): - try: + with suppress(Exception): os.makedirs(assets_folder, exist_ok=True) - except Exception: - pass pytest_style_css = os.path.join(assets_folder, "pytest_style.css") add_pytest_style_css = True if os.path.exists(pytest_style_css): @@ -1897,12 +1922,10 @@ def pytest_collection_finish(session): dash_path = os.path.join(os.getcwd(), "dashboard.html") dash_url = "file://" + dash_path.replace("\\", "/") star_len = len("Dashboard: ") + len(dash_url) - try: + with suppress(Exception): terminal_size = os.get_terminal_size().columns if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size - except Exception: - pass stars = "*" * star_len c1 = "" cr = "" @@ -1944,11 +1967,11 @@ def pytest_runtest_teardown(item): (Has zero effect on tests using --reuse-session / --rs)""" if "--co" in sys_argv or "--collect-only" in sys_argv: return - try: + with suppress(Exception): if hasattr(item, "_testcase") or hasattr(sb_config, "_sb_pdb_driver"): if hasattr(item, "_testcase"): self = item._testcase - try: + with suppress(Exception): if ( hasattr(self, "driver") and self.driver @@ -1956,22 +1979,18 @@ def pytest_runtest_teardown(item): ): if not (is_windows or self.driver.service.process): self.driver.quit() - except Exception: - pass elif ( hasattr(sb_config, "_sb_pdb_driver") and sb_config._sb_pdb_driver ): - try: + with suppress(Exception): if ( not is_windows or sb_config._sb_pdb_driver.service.process ): sb_config._sb_pdb_driver.quit() sb_config._sb_pdb_driver = None - except Exception: - pass - try: + with suppress(Exception): if ( hasattr(self, "_xvfb_display") and self._xvfb_display @@ -1988,10 +2007,6 @@ def pytest_runtest_teardown(item): ): sb_config._virtual_display.stop() sb_config._virtual_display = None - except Exception: - pass - except Exception: - pass if ( ( sb_config._has_exception @@ -2372,7 +2387,7 @@ def pytest_runtest_makereport(item, call): ) if log_path: sb_config._log_fail_data() - try: + with suppress(Exception): extra_report = None if hasattr(item, "_testcase"): extra_report = item._testcase._html_report_extra @@ -2417,5 +2432,3 @@ def pytest_runtest_makereport(item, call): "" % constants.Dashboard.LIVE_JS ) report.extra.append(pytest_html.extras.html(refresh_updates)) - except Exception: - pass diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 63355b1ce2a..4427910fc0e 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -7,7 +7,7 @@ Example --> -``` +```python from seleniumbase import SB with SB() as sb: # Many args! Eg. SB(browser="edge") @@ -41,7 +41,7 @@ def SB( proxy=None, # Use proxy. Format: "SERVER:PORT" or "USER:PASS@SERVER:PORT". proxy_bypass_list=None, # Skip proxy when using the listed domains. proxy_pac_url=None, # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL) - multi_proxy=False, # Allow multiple proxies with auth when multi-threaded. + multi_proxy=None, # Allow multiple proxies with auth when multi-threaded. agent=None, # Modify the web browser's User-Agent string. cap_file=None, # The desired capabilities to use with a Selenium Grid. cap_string=None, # The desired capabilities to use with a Selenium Grid. @@ -79,10 +79,13 @@ def SB( wait_for_angularjs=None, # Wait for AngularJS to load after some actions. use_wire=None, # Use selenium-wire's webdriver over selenium webdriver. external_pdf=None, # Set Chrome "plugins.always_open_pdf_externally":True. + window_position=None, # Set the browser's starting window position: "X,Y" + window_size=None, # Set the browser's starting window size: "Width,Height" is_mobile=None, # Use the mobile device emulator while running tests. mobile=None, # Shortcut / Duplicate of "is_mobile". device_metrics=None, # Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio" xvfb=None, # Run tests using the Xvfb virtual display server on Linux OS. + xvfb_metrics=None, # Set Xvfb display size on Linux: "Width,Height". start_page=None, # The starting URL for the web browser when tests begin. rec_print=None, # If Recorder is enabled, prints output after tests end. rec_behave=None, # Like Recorder Mode, but also generates behave-gherkin. @@ -124,6 +127,124 @@ def SB( interval=None, # SECONDS (Autoplay interval for SB Slides & Tour steps.) time_limit=None, # SECONDS (Safely fail tests that exceed the time limit.) ): + """ + * SeleniumBase as a Python Context Manager * + + Example: + -------- + .. code-block:: python + from seleniumbase import SB + + with SB() as sb: # Many args! Eg. SB(browser="edge") + sb.open("https://google.com/ncr") + sb.type('[name="q"]', "SeleniumBase on GitHub") + sb.submit('[name="q"]') + sb.click('a[href*="github.com/seleniumbase"]') + sb.highlight("div.Layout-main") + sb.highlight("div.Layout-sidebar") + sb.sleep(0.5) + + Optional Parameters: + -------------------- + test: Test Mode: Output, Logging, Continue on failure unless "rtf". + rtf: Shortcut / Duplicate of "raise_test_failure". + raise_test_failure: If "test" mode, raise Exception on 1st failure. + browser: Choose from "chrome", "edge", "firefox", or "safari". + headless: The original headless mode for Chromium and Firefox. + headless2: Chromium's new headless mode. (Has more features) + locale_code: Set the Language Locale Code for the web browser. + protocol: The Selenium Grid protocol: "http" or "https". + servername: The Selenium Grid server/IP used for tests. + port: The Selenium Grid port used by the test server. + proxy: Use proxy. Format: "SERVER:PORT" or "USER:PASS@SERVER:PORT". + proxy_bypass_list: Skip proxy when using the listed domains. + proxy_pac_url: Use PAC file. (Format: URL or USERNAME:PASSWORD@URL) + multi_proxy: # Allow multiple proxies with auth when multi-threaded. + agent: Modify the web browser's User-Agent string. + cap_file: The desired capabilities to use with a Selenium Grid. + cap_string: The desired capabilities to use with a Selenium Grid. + recorder_ext: Enables the SeleniumBase Recorder Chromium extension. + disable_js: Disable JavaScript on websites. Pages might break! + disable_csp: Disable the Content Security Policy of websites. + enable_ws: Enable Web Security on Chromium-based browsers. + enable_sync: Enable "Chrome Sync" on websites. + use_auto_ext: Use Chrome's automation extension. + undetectable: Use undetected-chromedriver to evade bot-detection. + uc_cdp_events: Capture CDP events in undetected-chromedriver mode. + uc_subprocess: Use undetected-chromedriver as a subprocess. + log_cdp_events: Capture {"performance": "ALL", "browser": "ALL"} + incognito: Enable Chromium's Incognito mode. + guest_mode: Enable Chromium's Guest mode. + dark_mode: Enable Chromium's Dark mode. + devtools: Open Chromium's DevTools when the browser opens. + remote_debug: Enable Chrome's Debugger on "http://localhost:9222". + enable_3d_apis: Enable WebGL and 3D APIs. + swiftshader: Chrome: --use-gl=angle / --use-angle=swiftshader-webgl + ad_block_on: Block some types of display ads from loading. + host_resolver_rules: Set host-resolver-rules, comma-separated. + block_images: Block images from loading during tests. + do_not_track: Tell websites that you don't want to be tracked. + chromium_arg: "ARG=N,ARG2" (Set Chromium args, ","-separated.) + firefox_arg: "ARG=N,ARG2" (Set Firefox args, comma-separated.) + firefox_pref: SET (Set Firefox PREFERENCE:VALUE set, ","-separated) + user_data_dir: Set the Chrome user data directory to use. + extension_zip: Load a Chrome Extension .zip|.crx, comma-separated. + extension_dir: Load a Chrome Extension directory, comma-separated. + disable_features: "F1,F2" (Disable Chrome features, ","-separated.) + binary_location: Set path of the Chromium browser binary to use. + driver_version: Set the chromedriver or uc_driver version to use. + skip_js_waits: Skip JS Waits (readyState=="complete" and Angular). + wait_for_angularjs: Wait for AngularJS to load after some actions. + use_wire: Use selenium-wire's webdriver over selenium webdriver. + external_pdf: Set Chrome "plugins.always_open_pdf_externally":True. + window_position: Set the browser's starting window position: "X,Y" + window_size: Set the browser's starting window size: "Width,Height" + is_mobile: Use the mobile device emulator while running tests. + mobile: Shortcut / Duplicate of "is_mobile". + device_metrics: Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio" + xvfb: Run tests using the Xvfb virtual display server on Linux OS. + xvfb_metrics: Set Xvfb display size on Linux: "Width,Height". + start_page: The starting URL for the web browser when tests begin. + rec_print: If Recorder is enabled, prints output after tests end. + rec_behave: Like Recorder Mode, but also generates behave-gherkin. + record_sleep: If Recorder enabled, also records self.sleep calls. + data: Extra test data. Access with "self.data" in tests. + var1: Extra test data. Access with "self.var1" in tests. + var2: Extra test data. Access with "self.var2" in tests. + var3: Extra test data. Access with "self.var3" in tests. + variables: DICT (Extra test data. Access with "self.variables") + account: Set account. Access with "self.account" in tests. + environment: Set the test env. Access with "self.env" in tests. + headed: Run tests in headed/GUI mode on Linux, where not default. + maximize: Start tests with the browser window maximized. + disable_ws: Reverse of "enable_ws". (None and False are different) + disable_beforeunload: Disable the "beforeunload" event on Chromium. + settings_file: A file for overriding default SeleniumBase settings. + uc: Shortcut / Duplicate of "undetectable". + undetected: Shortcut / Duplicate of "undetectable". + uc_cdp: Shortcut / Duplicate of "uc_cdp_events". + uc_sub: Shortcut / Duplicate of "uc_subprocess". + log_cdp: Shortcut / Duplicate of "log_cdp_events". + ad_block: Shortcut / Duplicate of "ad_block_on". + server: Shortcut / Duplicate of "servername". + guest: Shortcut / Duplicate of "guest_mode". + wire: Shortcut / Duplicate of "use_wire". + pls: Shortcut / Duplicate of "page_load_strategy". + sjw: Shortcut / Duplicate of "skip_js_waits". + wfa: Shortcut / Duplicate of "wait_for_angularjs". + save_screenshot: Save a screenshot at the end of each test. + no_screenshot: No screenshots saved unless tests directly ask it. + page_load_strategy: Set Chrome PLS to "normal", "eager", or "none". + timeout_multiplier: Multiplies the default timeout values. + js_checking_on: Check for JavaScript errors after page loads. + slow: Slow down the automation. Faster than using Demo Mode. + demo: Slow down and visually see test actions as they occur. + demo_sleep: SECONDS (Set wait time after Slow & Demo Mode actions.) + message_duration: SECONDS (The time length for Messenger alerts.) + highlights: Number of highlight animations for Demo Mode actions. + interval: SECONDS (Autoplay interval for SB Slides & Tour steps.) + time_limit: SECONDS (Safely fail tests that exceed the time limit.) + """ import os import sys import time @@ -365,6 +486,48 @@ def SB( break count += 1 disable_features = d_f + w_p = window_position + if w_p is None and "--window-position" in arg_join: + count = 0 + for arg in sys_argv: + if arg.startswith("--window-position="): + w_p = arg.split("--window-position=")[1] + break + elif arg == "--window-position" and len(sys_argv) > count + 1: + w_p = sys_argv[count + 1] + if w_p.startswith("-"): + w_p = None + break + count += 1 + window_position = w_p + w_s = window_size + if w_s is None and "--window-size" in arg_join: + count = 0 + for arg in sys_argv: + if arg.startswith("--window-size="): + w_s = arg.split("--window-size=")[1] + break + elif arg == "--window-size" and len(sys_argv) > count + 1: + w_s = sys_argv[count + 1] + if w_s.startswith("-"): + w_s = None + break + count += 1 + window_size = w_s + x_m = xvfb_metrics + if x_m is None and "--xvfb-metrics" in arg_join: + count = 0 + for arg in sys_argv: + if arg.startswith("--xvfb-metrics="): + x_m = arg.split("--xvfb-metrics=")[1] + break + elif arg == "--xvfb-metrics" and len(sys_argv) > count + 1: + x_m = sys_argv[count + 1] + if x_m.startswith("-"): + x_m = None + break + count += 1 + xvfb_metrics = x_m if agent is None and "--agent" in arg_join: count = 0 for arg in sys_argv: @@ -761,6 +924,7 @@ def SB( sb_config.headless2 = headless2 sb_config.headed = headed sb_config.xvfb = xvfb + sb_config.xvfb_metrics = xvfb_metrics sb_config.start_page = start_page sb_config.locale_code = locale_code sb_config.protocol = protocol @@ -803,7 +967,8 @@ def SB( sb_config.crumbs = False sb_config.final_debug = False sb_config.visual_baseline = False - sb_config.window_size = None + sb_config.window_position = window_position + sb_config.window_size = window_size sb_config.maximize_option = maximize_option sb_config._disable_beforeunload = _disable_beforeunload sb_config.save_screenshot = save_screenshot @@ -864,6 +1029,7 @@ def SB( sb.headless2 = sb_config.headless2 sb.headed = sb_config.headed sb.xvfb = sb_config.xvfb + sb.xvfb_metrics = sb_config.xvfb_metrics sb.start_page = sb_config.start_page sb.locale_code = sb_config.locale_code sb.protocol = sb_config.protocol @@ -908,6 +1074,7 @@ def SB( sb._crumbs = sb_config.crumbs sb._final_debug = sb_config.final_debug sb.visual_baseline = sb_config.visual_baseline + sb.window_position = sb_config.window_position sb.window_size = sb_config.window_size sb.maximize_option = sb_config.maximize_option sb._disable_beforeunload = sb_config._disable_beforeunload diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 0dd373f9d21..edbd340a9c6 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -1,5 +1,6 @@ """Selenium Plugin for SeleniumBase tests that run with pynose / nosetests""" import sys +from contextlib import suppress from nose.plugins import Plugin from seleniumbase import config as sb_config from seleniumbase.config import settings @@ -46,6 +47,7 @@ class SeleniumBrowser(Plugin): --headless2 (Use the new headless mode, which supports extensions.) --headed (Run tests in headed/GUI mode on Linux OS, where not default.) --xvfb (Run tests using the Xvfb virtual display server on Linux OS.) + --xvfb-metrics=STRING (Set Xvfb display size on Linux: "Width,Height".) --locale=LOCALE_CODE (Set the Language Locale Code for the web browser.) --interval=SECONDS (The autoplay interval for presentations & tour steps) --start-page=URL (The starting URL for the web browser when tests begin.) @@ -82,6 +84,7 @@ class SeleniumBrowser(Plugin): --dark (Enable Chrome's Dark mode.) --devtools (Open Chrome's DevTools when the browser opens.) --disable-beforeunload (Disable the "beforeunload" event on Chrome.) + --window-position=X,Y (Set the browser's starting window position.) --window-size=WIDTH,HEIGHT (Set the browser's starting window size.) --maximize (Start tests with the browser window maximized.) --screenshot (Save a screenshot at the end of each test.) @@ -465,6 +468,17 @@ def options(self, parser, env): will no longer be enabled by default on Linux. Default: False. (Linux-ONLY!)""", ) + parser.addoption( + "--xvfb-metrics", + "--xvfb_metrics", + action="store", + dest="xvfb_metrics", + default=None, + help="""Customize the Xvfb metrics (Width,Height) on Linux. + Format: A comma-separated string with the 2 values. + Examples: "1920,1080" or "1366,768" or "1024,768". + Default: None. (None: "1366,768". Min: "1024,768".)""", + ) parser.addoption( "--locale_code", "--locale-code", @@ -886,6 +900,17 @@ def options(self, parser, env): on Chromium browsers (Chrome or Edge). This is already the default Firefox option.""", ) + parser.addoption( + "--window-position", + "--window_position", + action="store", + dest="window_position", + default=None, + help="""The option to set the starting window x,y position. + Format: A comma-separated string with the 2 values. + Example: "55,66" + Default: None. (Will use default values if None)""", + ) parser.addoption( "--window-size", "--window_size", @@ -1060,6 +1085,29 @@ def beforeTest(self, test): '\n (Your browser choice was: "%s")\n' % browser ) raise Exception(message) + window_position = self.options.window_position + if window_position: + if window_position.count(",") != 1: + message = ( + '\n\n window_position expects an "x,y" string!' + '\n (Your input was: "%s")\n' % window_position + ) + raise Exception(message) + window_position = window_position.replace(" ", "") + win_x = None + win_y = None + try: + win_x = int(window_position.split(",")[0]) + win_y = int(window_position.split(",")[1]) + except Exception: + message = ( + '\n\n Expecting integer values for "x,y"!' + '\n (window_position input was: "%s")\n' + % window_position + ) + raise Exception(message) + settings.WINDOW_START_X = win_x + settings.WINDOW_START_Y = win_y window_size = self.options.window_size if window_size: if window_size.count(",") != 1: @@ -1112,6 +1160,7 @@ def beforeTest(self, test): self.options.headless2 = False test.test.headed = self.options.headed test.test.xvfb = self.options.xvfb + test.test.xvfb_metrics = self.options.xvfb_metrics test.test.locale_code = self.options.locale_code test.test.interval = self.options.interval test.test.start_page = self.options.start_page @@ -1192,6 +1241,7 @@ def beforeTest(self, test): test.test.dark_mode = self.options.dark_mode test.test.devtools = self.options.devtools test.test._disable_beforeunload = self.options._disable_beforeunload + test.test.window_position = self.options.window_position test.test.window_size = self.options.window_size test.test.maximize_option = self.options.maximize_option if self.options.save_screenshot and self.options.no_screenshot: @@ -1259,7 +1309,7 @@ def beforeTest(self, test): ): width = settings.HEADLESS_START_WIDTH height = settings.HEADLESS_START_HEIGHT - try: + with suppress(Exception): from sbvirtualdisplay import Display self._xvfb_display = Display(visible=0, size=(width, height)) @@ -1267,8 +1317,6 @@ def beforeTest(self, test): sb_config._virtual_display = self._xvfb_display self.headless_active = True sb_config.headless_active = True - except Exception: - pass sb_config._is_timeout_changed = False sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT @@ -1301,7 +1349,7 @@ def afterTest(self, test): pass except Exception: pass - try: + with suppress(Exception): if ( hasattr(self, "_xvfb_display") and self._xvfb_display @@ -1318,5 +1366,3 @@ def afterTest(self, test): ): sb_config._virtual_display.stop() sb_config._virtual_display = None - except Exception: - pass diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 40283f43350..36d6d3a5977 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -10,6 +10,7 @@ import selenium.webdriver.chrome.webdriver import selenium.webdriver.common.service import selenium.webdriver.remote.command +from contextlib import suppress from .cdp import CDP from .cdp import PageElement from .dprocess import start_detached @@ -201,11 +202,9 @@ def __init__( # Create a temporary folder for the user-data profile. options.add_argument(arg) if not language: - try: + with suppress(Exception): import locale language = locale.getlocale()[0].replace("_", "-") - except Exception: - pass if ( not language or "English" in language @@ -242,7 +241,7 @@ def __init__( ) if hasattr(options, 'handle_prefs'): options.handle_prefs(user_data_dir) - try: + with suppress(Exception): import json with open( os.path.join( @@ -263,8 +262,6 @@ def __init__( fs.seek(0, 0) fs.truncate() json.dump(config, fs) - except Exception: - pass creationflags = 0 if "win32" in sys.platform: creationflags = subprocess.CREATE_NO_WINDOW @@ -293,8 +290,6 @@ def __init__( self.browser_pid = browser.pid service_ = None log_output = subprocess.PIPE - if sys.version_info < (3, 8): - log_output = os.devnull if patch_driver: service_ = selenium.webdriver.chrome.service.Service( executable_path=self.patcher.executable_path, @@ -342,7 +337,7 @@ def __dir__(self): def _get_cdc_props(self): cdc_props = [] - try: + with suppress(Exception): cdc_props = self.execute_script( """ let objectToInspect = window, @@ -355,8 +350,6 @@ def _get_cdc_props(self): return result.filter(i => i.match(/^[a-z]{3}_[a-z]{22}_.*/i)) """ ) - except Exception: - pass return cdc_props def _hook_remove_cdc_props(self, cdc_props): @@ -427,50 +420,38 @@ def reconnect(self, timeout=0.1): - Starts the chromedriver service that runs in the background. - Recreates the session.""" if hasattr(self, "service"): - try: + with suppress(Exception): self.service.stop() - except Exception: - pass if isinstance(timeout, str): if timeout.lower() == "breakpoint": breakpoint() # To continue: pass # Type "c" & press ENTER! else: time.sleep(timeout) - try: + with suppress(Exception): self.service.start() - except Exception: - pass time.sleep(0.012) - try: + with suppress(Exception): self.start_session() - except Exception: - pass time.sleep(0.012) def disconnect(self): """Stops the chromedriver service that runs in the background. To use driver methods again, you MUST call driver.connect()""" if hasattr(self, "service"): - try: + with suppress(Exception): self.service.stop() - except Exception: - pass time.sleep(0.012) def connect(self): """Starts the chromedriver service that runs in the background and recreates the session.""" if hasattr(self, "service"): - try: + with suppress(Exception): self.service.start() - except Exception: - pass time.sleep(0.012) - try: + with suppress(Exception): self.start_session() - except Exception: - pass time.sleep(0.012) def start_session(self, capabilities=None): @@ -496,12 +477,10 @@ def quit(self): if hasattr(self, "service") and getattr(self.service, "process", None): logger.debug("Stopping webdriver service") self.service.stop() - try: + with suppress(Exception): if self.reactor and isinstance(self.reactor, Reactor): logger.debug("Shutting down Reactor") self.reactor.event.set() - except Exception: - pass if ( hasattr(self, "keep_user_data_dir") and hasattr(self, "user_data_dir") @@ -530,18 +509,14 @@ def quit(self): self.patcher = None def __del__(self): - try: + with suppress(Exception): if "win32" in sys.platform: self.stop_client() self.command_executor.close() else: super().quit() - except Exception: - pass - try: + with suppress(Exception): self.quit() - except Exception: - pass def __enter__(self): return self diff --git a/seleniumbase/undetected/dprocess.py b/seleniumbase/undetected/dprocess.py index acfdc558cd2..dc6009b8962 100644 --- a/seleniumbase/undetected/dprocess.py +++ b/seleniumbase/undetected/dprocess.py @@ -3,7 +3,10 @@ import sys import atexit import logging +import platform +from contextlib import suppress from subprocess import PIPE +from subprocess import Popen CREATE_NEW_PROCESS_GROUP = 0x00000200 DETACHED_PROCESS = 0x00000008 @@ -37,9 +40,6 @@ def start_detached(executable, *args): def _start_detached(executable, *args, writer=None): # Configure Launch kwargs = {} - import platform - from subprocess import Popen - if platform.system() == "Windows": kwargs.update( creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP @@ -58,11 +58,9 @@ def _cleanup(): import signal for pid in REGISTERED: - try: + with suppress(Exception): logging.getLogger(__name__).debug("cleaning up pid %d " % pid) os.kill(pid, signal.SIGTERM) - except Exception: - pass atexit.register(_cleanup) diff --git a/seleniumbase/undetected/options.py b/seleniumbase/undetected/options.py index b93f3966c73..2f83cec71f3 100644 --- a/seleniumbase/undetected/options.py +++ b/seleniumbase/undetected/options.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import json import os +from contextlib import suppress from selenium.webdriver.chromium.options import ChromiumOptions @@ -49,7 +50,7 @@ def handle_prefs(self, user_data_dir): undot_prefs, self._undot_key(key, value) ) prefs_file = os.path.join(default_path, "Preferences") - try: + with suppress(Exception): if os.path.exists(prefs_file): with open( prefs_file, encoding="utf-8", mode="r", errors="ignore" @@ -57,13 +58,9 @@ def handle_prefs(self, user_data_dir): undot_prefs = self._merge_nested( json.load(f), undot_prefs ) - except Exception: - pass - try: + with suppress(Exception): with open(prefs_file, encoding="utf-8", mode="w") as f: json.dump(undot_prefs, f) - except Exception: - pass # Remove experimental_options to avoid errors del self._experimental_options["prefs"] exclude_switches = self.experimental_options.get("excludeSwitches") diff --git a/seleniumbase/undetected/patcher.py b/seleniumbase/undetected/patcher.py index 11ffa1e2ce2..1375e177b61 100644 --- a/seleniumbase/undetected/patcher.py +++ b/seleniumbase/undetected/patcher.py @@ -8,6 +8,7 @@ import sys import time import zipfile +from contextlib import suppress logger = logging.getLogger(__name__) IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux")) @@ -53,10 +54,8 @@ def __init__(self, executable_path=None, force=False, version_main=0): self.executable_path = None prefix = "undetected" if not os.path.exists(self.data_path): - try: + with suppress(Exception): os.makedirs(self.data_path, exist_ok=True) - except Exception: - pass if not executable_path: self.executable_path = os.path.join( self.data_path, "_".join([prefix, self.exe_name]) diff --git a/setup.py b/setup.py index 6066e850fb1..2ddf2005dd7 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """Setup steps for installing SeleniumBase dependencies and plugins. -(Uses selenium 4.x and is compatible with Python 3.7+)""" +(Uses selenium 4.x and is compatible with Python 3.8+)""" from setuptools import setup, find_packages # noqa: F401 import os import sys @@ -47,7 +47,7 @@ print("\n*** Installing build: *** (Required for PyPI uploads)\n") os.system("python -m pip install --upgrade 'build'") print("\n*** Installing pkginfo: *** (Required for PyPI uploads)\n") - os.system("python -m pip install --upgrade 'pkginfo'") + os.system("python -m pip install 'pkginfo'") print("\n*** Installing readme-renderer: *** (For PyPI uploads)\n") os.system("python -m pip install --upgrade 'readme-renderer'") print("\n*** Installing jaraco.classes: *** (For PyPI uploads)\n") @@ -79,12 +79,14 @@ long_description_content_type="text/markdown", url="https://github.com/seleniumbase/SeleniumBase", project_urls={ + "Homepage": "https://github.com/seleniumbase/SeleniumBase", "Changelog": "https://github.com/seleniumbase/SeleniumBase/releases", "Download": "https://pypi.org/project/seleniumbase/#files", - "Gitter": "https://gitter.im/seleniumbase/SeleniumBase", "Blog": "https://seleniumbase.com/", + "Discord": "https://discord.gg/EdhQTn3EyE", "PyPI": "https://pypi.org/project/seleniumbase/", "Source": "https://github.com/seleniumbase/SeleniumBase", + "Repository": "https://github.com/seleniumbase/SeleniumBase", "Documentation": "https://seleniumbase.io/", }, platforms=["Windows", "Linux", "Mac OS-X"], @@ -120,7 +122,6 @@ "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -144,82 +145,64 @@ "Topic :: Software Development :: Testing :: Traffic Generation", "Topic :: Utilities", ], - python_requires=">=3.7", + python_requires=">=3.8", install_requires=[ - 'pip>=24.0;python_version<"3.8"', - 'pip>=24.1.2;python_version>="3.8"', # 24.2: editable install warnings - 'packaging>=24.0;python_version<"3.8"', - 'packaging>=24.1;python_version>="3.8"', - 'setuptools>=68.0.0;python_version<"3.8"', - 'setuptools~=70.2;python_version>="3.8" and python_version<"3.10"', + 'pip>=24.1.2', # 24.2: editable install warnings + 'packaging>=24.1', + 'setuptools~=70.2;python_version<"3.10"', 'setuptools>=70.2.0;python_version>="3.10"', # 71.0.x has issues - 'wheel>=0.42.0;python_version<"3.8"', - 'wheel>=0.44.0;python_version>="3.8"', + 'wheel>=0.44.0', 'attrs>=24.2.0', "certifi>=2024.8.30", "exceptiongroup>=1.2.2", - 'filelock>=3.12.2;python_version<"3.8"', - 'filelock>=3.16.0;python_version>="3.8"', - 'platformdirs>=4.0.0;python_version<"3.8"', - 'platformdirs>=4.3.2;python_version>="3.8"', - 'typing-extensions>=4.12.2;python_version>="3.8"', + 'filelock>=3.16.1', + 'fasteners>=0.19', + "pynose>=1.5.3", + 'platformdirs>=4.3.6', + 'typing-extensions>=4.12.2', + "sbvirtualdisplay>=1.3.0", + "six>=1.16.0", 'parse>=1.20.2', 'parse-type>=0.6.3', - 'pyyaml==6.0.1;python_version<"3.8"', - 'pyyaml>=6.0.2;python_version>="3.8"', - "six==1.16.0", - "idna==3.8", + 'colorama>=0.4.6', + 'pyyaml>=6.0.2', + 'pygments>=2.18.0', + 'pyreadline3>=3.5.3;platform_system=="Windows"', + "tabcompleter>=1.3.3", + "pdbp>=1.5.4", + "idna==3.10", 'chardet==5.2.0', 'charset-normalizer==3.3.2', 'urllib3>=1.26.20,<2;python_version<"3.10"', 'urllib3>=1.26.20,<2.3.0;python_version>="3.10"', 'requests==2.31.0', - "pynose==1.5.2", 'sniffio==1.3.1', 'h11==0.14.0', 'outcome==1.3.0.post0', - 'trio==0.22.2;python_version<"3.8"', - 'trio==0.26.2;python_version>="3.8"', + 'trio==0.26.2', 'trio-websocket==0.11.1', 'wsproto==1.2.0', - 'websocket-client==1.8.0;python_version>="3.8"', - 'selenium==4.11.2;python_version<"3.8"', - 'selenium==4.24.0;python_version>="3.8"', + 'websocket-client==1.8.0', + 'selenium==4.25.0', 'cssselect==1.2.0', "sortedcontainers==2.4.0", - 'fasteners==0.19', - 'execnet==2.0.2;python_version<"3.8"', - 'execnet==2.1.1;python_version>="3.8"', + 'execnet==2.1.1', 'iniconfig==2.0.0', - 'pluggy==1.2.0;python_version<"3.8"', - 'pluggy==1.5.0;python_version>="3.8"', + 'pluggy==1.5.0', "py==1.11.0", # Needed by pytest-html - 'pytest==7.4.4;python_version<"3.8"', - 'pytest==8.3.3;python_version>="3.8"', + 'pytest==8.3.3', "pytest-html==2.0.1", # Newer ones had issues - 'pytest-metadata==3.0.0;python_version<"3.8"', - 'pytest-metadata==3.1.1;python_version>="3.8"', + 'pytest-metadata==3.1.1', "pytest-ordering==0.6", - 'pytest-rerunfailures==13.0;python_version<"3.8"', - 'pytest-rerunfailures==14.0;python_version>="3.8"', - 'pytest-xdist==3.5.0;python_version<"3.8"', - 'pytest-xdist==3.6.1;python_version>="3.8"', + 'pytest-rerunfailures==14.0', + 'pytest-xdist==3.6.1', 'parameterized==0.9.0', - "sbvirtualdisplay==1.3.0", "behave==1.2.6", - 'soupsieve==2.4.1;python_version<"3.8"', - 'soupsieve==2.6;python_version>="3.8"', + 'soupsieve==2.6', "beautifulsoup4==4.12.3", - 'pygments==2.17.2;python_version<"3.8"', - 'pygments==2.18.0;python_version>="3.8"', - 'pyreadline3==3.4.1;platform_system=="Windows"', - "tabcompleter==1.3.3", - "pdbp==1.5.4", - 'colorama==0.4.6', 'pyotp==2.9.0', 'python-xlib==0.33;platform_system=="Linux"', - 'markdown-it-py==2.2.0;python_version<"3.8"', - 'markdown-it-py==3.0.0;python_version>="3.8"', + 'markdown-it-py==3.0.0', 'mdurl==0.1.2', 'rich==13.8.1', ], @@ -235,10 +218,8 @@ # pip install -e .[coverage] # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ - 'coverage==7.2.7;python_version<"3.8"', - 'coverage>=7.6.1;python_version>="3.8"', - 'pytest-cov==4.1.0;python_version<"3.8"', - 'pytest-cov>=5.0.0;python_version>="3.8"', + 'coverage>=7.6.1', + 'pytest-cov>=5.0.0', ], # pip install -e .[flake8] # Usage: flake8 @@ -260,19 +241,16 @@ # pip install -e .[pdfminer] # (An optional library for parsing PDF files.) "pdfminer": [ - 'pdfminer.six==20221105;python_version<"3.8"', - 'pdfminer.six==20240706;python_version>="3.8"', + 'pdfminer.six==20240706', 'cryptography==39.0.2;python_version<"3.9"', 'cryptography==43.0.1;python_version>="3.9"', - 'cffi==1.15.1;python_version<"3.8"', - 'cffi==1.17.1;python_version>="3.8"', + 'cffi==1.17.1', "pycparser==2.22", ], # pip install -e .[pillow] # (An optional library for image-processing.) "pillow": [ - 'Pillow==9.5.0;python_version<"3.8"', - 'Pillow>=10.4.0;python_version>="3.8"', + 'Pillow>=10.4.0', ], # pip install -e .[pip-system-certs] # (If you see [SSL: CERTIFICATE_VERIFY_FAILED], then get this.) @@ -309,8 +287,7 @@ 'hpack==4.0.0', 'hyperframe==6.0.1', 'kaitaistruct==0.10', - 'pyasn1==0.5.1;python_version<"3.8"', - 'pyasn1==0.6.1;python_version>="3.8"', + 'pyasn1==0.6.1', 'zstandard==0.23.0', ], },