diff --git a/examples/tour_examples/bootstrap_google_tour.py b/examples/tour_examples/bootstrap_google_tour.py index 4219e39fb15..9b8882dc74d 100644 --- a/examples/tour_examples/bootstrap_google_tour.py +++ b/examples/tour_examples/bootstrap_google_tour.py @@ -34,6 +34,8 @@ def test_google_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') self.create_bootstrap_tour() self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") diff --git a/examples/tour_examples/driverjs_maps_tour.py b/examples/tour_examples/driverjs_maps_tour.py index db7765dd533..cb26cb79efa 100644 --- a/examples/tour_examples/driverjs_maps_tour.py +++ b/examples/tour_examples/driverjs_maps_tour.py @@ -8,6 +8,8 @@ def test_create_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') # Create a website tour using the DriverJS library # Same as: self.create_driverjs_tour() diff --git a/examples/tour_examples/google_tour.py b/examples/tour_examples/google_tour.py index 10b422ac8ca..03081b10959 100644 --- a/examples/tour_examples/google_tour.py +++ b/examples/tour_examples/google_tour.py @@ -40,6 +40,8 @@ def test_google_tour(self): self.wait_for_element("#searchboxinput") self.wait_for_element("#minimap") self.wait_for_element("#zoom") + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') # Create a website tour using the IntroJS library # Same as: self.create_introjs_tour() diff --git a/examples/tour_examples/hopscotch_google_tour.py b/examples/tour_examples/hopscotch_google_tour.py index 3647c757469..7aa8743ab3b 100644 --- a/examples/tour_examples/hopscotch_google_tour.py +++ b/examples/tour_examples/hopscotch_google_tour.py @@ -34,6 +34,8 @@ def test_google_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') self.create_hopscotch_tour() self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") diff --git a/examples/tour_examples/introjs_google_tour.py b/examples/tour_examples/introjs_google_tour.py index f69635e6a31..11136d19398 100644 --- a/examples/tour_examples/introjs_google_tour.py +++ b/examples/tour_examples/introjs_google_tour.py @@ -34,6 +34,8 @@ def test_google_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') self.set_introjs_colors("#f26721", "#db5409") self.create_introjs_tour() diff --git a/examples/tour_examples/maps_introjs_tour.py b/examples/tour_examples/maps_introjs_tour.py index d073d764e08..570138a4190 100644 --- a/examples/tour_examples/maps_introjs_tour.py +++ b/examples/tour_examples/maps_introjs_tour.py @@ -8,6 +8,8 @@ def test_google_maps_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') self.create_tour(theme="introjs") self.add_tour_step( diff --git a/examples/tour_examples/shepherd_google_tour.py b/examples/tour_examples/shepherd_google_tour.py index bc660d6ff53..3a9878fe6b9 100644 --- a/examples/tour_examples/shepherd_google_tour.py +++ b/examples/tour_examples/shepherd_google_tour.py @@ -34,6 +34,8 @@ def test_google_tour(self): self.wait_for_element("#searchboxinput", timeout=20) self.wait_for_element("#minimap", timeout=20) self.wait_for_element("#zoom", timeout=20) + self.wait_for_element("#widget-zoom-out") + self.wait_for_element('[jsaction*="minimap.main;"]') self.create_shepherd_tour(theme="dark") self.add_tour_step("Welcome to Google Maps!") diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index dfb2338b4bf..eafb5eb76a7 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -20,7 +20,7 @@ lxml==5.3.0 pyquery==2.0.1 readtime==3.0.0 mkdocs==1.6.1 -mkdocs-material==9.5.47 +mkdocs-material==9.5.48 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3.1 diff --git a/requirements.txt b/requirements.txt index cc26b99f660..22e6a73191f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -64,7 +64,7 @@ rich==13.9.4 # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) coverage>=7.6.1;python_version<"3.9" -coverage>=7.6.8;python_version>="3.9" +coverage>=7.6.9;python_version>="3.9" pytest-cov>=5.0.0;python_version<"3.9" pytest-cov>=6.0.0;python_version>="3.9" flake8==5.0.4;python_version<"3.9" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 093f5cfc1fc..0785261842d 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.33.5" +__version__ = "4.33.6" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index e4aad9e0f0b..6133be101c5 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -97,26 +97,12 @@ def log_d(message): print(message) -def make_writable(file_path): - # Set permissions to: "If you can read it, you can write it." - mode = os.stat(file_path).st_mode - mode |= (mode & 0o444) >> 1 # copy R bits to W - os.chmod(file_path, mode) - - -def make_executable(file_path): - # Set permissions to: "If you can read it, you can execute it." - mode = os.stat(file_path).st_mode - mode |= (mode & 0o444) >> 2 # copy R bits to X - os.chmod(file_path, mode) - - def make_driver_executable_if_not(driver_path): # Verify driver has executable permissions. If not, add them. permissions = oct(os.stat(driver_path)[0])[-3:] if "4" in permissions or "6" in permissions: # We want at least a '5' or '7' to make sure it's executable - make_executable(driver_path) + shared_utils.make_executable(driver_path) def extend_driver(driver): @@ -566,6 +552,10 @@ def uc_open_with_cdp_mode(driver, url=None): for tab in driver.cdp_base.tabs[-1::-1]: if "chrome-extension://" not in str(tab): with gui_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.PYAUTOGUILOCK + ) loop.run_until_complete(tab.activate()) break @@ -580,11 +570,17 @@ def uc_open_with_cdp_mode(driver, url=None): if page_tab: loop.run_until_complete(page_tab.aopen()) with gui_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.PYAUTOGUILOCK + ) loop.run_until_complete(page_tab.activate()) loop.run_until_complete(driver.cdp_base.update_targets()) page = loop.run_until_complete(driver.cdp_base.get(url)) with gui_lock: + with suppress(Exception): + shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK) loop.run_until_complete(page.activate()) loop.run_until_complete(page.wait()) if not safe_url: @@ -883,17 +879,12 @@ def install_pyautogui_if_missing(driver): with pip_find_lock: pass except Exception: - # Need write permissions - with suppress(Exception): - make_writable(constants.PipInstall.FINDLOCK) - try: - with pip_find_lock: - pass - except Exception: - # Since missing permissions, skip the locks - __install_pyautogui_if_missing() - return + # Since missing permissions, skip the locks + __install_pyautogui_if_missing() + return with pip_find_lock: # Prevent issues with multiple processes + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) __install_pyautogui_if_missing() @@ -1789,6 +1780,8 @@ def _add_chrome_proxy_extension( if zip_it: proxy_zip_lock = fasteners.InterProcessLock(PROXY_ZIP_LOCK) with proxy_zip_lock: + with suppress(Exception): + shared_utils.make_writable(PROXY_ZIP_LOCK) if multi_proxy: _set_proxy_filenames() if not os.path.exists(proxy_helper.PROXY_ZIP_PATH): @@ -1800,6 +1793,8 @@ def _add_chrome_proxy_extension( else: proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK) with proxy_dir_lock: + with suppress(Exception): + shared_utils.make_writable(PROXY_DIR_LOCK) if multi_proxy: _set_proxy_filenames() if not os.path.exists(proxy_helper.PROXY_DIR_PATH): @@ -1825,6 +1820,8 @@ def is_using_uc(undetectable, browser_name): def _unzip_to_new_folder(zip_file, folder): proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK) with proxy_dir_lock: + with suppress(Exception): + shared_utils.make_writable(PROXY_DIR_LOCK) if not os.path.exists(folder): import zipfile zip_ref = zipfile.ZipFile(zip_file, "r") @@ -2934,6 +2931,8 @@ def get_remote_driver( constants.PipInstall.FINDLOCK ) with pip_find_lock: # Prevent issues with multiple processes + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) try: from seleniumwire import webdriver import blinker @@ -3371,6 +3370,8 @@ def get_local_driver( constants.PipInstall.FINDLOCK ) with pip_find_lock: # Prevent issues with multiple processes + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) try: from seleniumwire import webdriver import blinker @@ -3434,6 +3435,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with geckodriver_fixing_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) if not geckodriver_on_path(): sys_args = sys.argv # Save a copy of sys args log_d( @@ -3736,6 +3741,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with edgedriver_fixing_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) msg = "Microsoft Edge Driver not found." if edgedriver_upgrade_needed: msg = "Microsoft Edge Driver update needed." @@ -4119,6 +4128,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with edgedriver_fixing_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) with suppress(Exception): if not _was_driver_repaired(): _repair_edgedriver(edge_version) @@ -4501,6 +4514,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with chromedriver_fixing_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) msg = "chromedriver update needed. Getting it now:" if not path_chromedriver: msg = "chromedriver not found. Getting it now:" @@ -4592,6 +4609,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with uc_lock: # Avoid multithreaded issues + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) if make_uc_driver_from_chromedriver: if os.path.exists(LOCAL_CHROMEDRIVER): with suppress(Exception): @@ -4851,6 +4872,10 @@ def get_local_driver( if not os.path.exists(cf_lock_path): # Avoid multithreaded issues with cf_lock: + with suppress(Exception): + shared_utils.make_writable( + cf_lock_path + ) # Install Python Certificates (MAC) os.system( r"bash /Applications/Python*/" @@ -4994,6 +5019,10 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with chromedriver_fixing_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) if not _was_driver_repaired(): _repair_chromedriver( chrome_options, headless_options, mcv @@ -5192,7 +5221,10 @@ def get_local_driver( chromedr_fixing_lock = fasteners.InterProcessLock( constants.MultiBrowser.DRIVER_FIXING_LOCK ) + D_F_L = constants.MultiBrowser.DRIVER_FIXING_LOCK with chromedr_fixing_lock: + with suppress(Exception): + shared_utils.make_writable(D_F_L) if not _was_driver_repaired(): with suppress(Exception): _repair_chromedriver( diff --git a/seleniumbase/core/log_helper.py b/seleniumbase/core/log_helper.py index e60e9448560..6c2b047d3db 100644 --- a/seleniumbase/core/log_helper.py +++ b/seleniumbase/core/log_helper.py @@ -339,9 +339,10 @@ def log_skipped_test_data(test, test_logpath, driver, browser, reason): data_to_save.append(" * Skip Reason: %s" % reason) data_to_save.append("") file_path = os.path.join(test_logpath, "skip_reason.txt") - log_file = codecs.open(file_path, "w+", encoding="utf-8") - log_file.writelines("\r\n".join(data_to_save)) - log_file.close() + with suppress(Exception): + log_file = codecs.open(file_path, "w+", encoding="utf-8") + log_file.writelines("\r\n".join(data_to_save)) + log_file.close() def log_page_source(test_logpath, driver, source=None): @@ -368,9 +369,10 @@ def log_page_source(test_logpath, driver, source=None): if not os.path.exists(test_logpath): os.makedirs(test_logpath) html_file_path = os.path.join(test_logpath, html_file_name) - html_file = codecs.open(html_file_path, "w+", encoding="utf-8") - html_file.write(page_source) - html_file.close() + with suppress(Exception): + html_file = codecs.open(html_file_path, "w+", encoding="utf-8") + html_file.write(page_source) + html_file.close() def get_test_id(test): diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 4318d35916b..18edd34b03d 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1019,6 +1019,10 @@ def set_attributes(self, selector, attribute, value): with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) + def __make_sure_pyautogui_lock_is_writable(self): + with suppress(Exception): + shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK) + def __verify_pyautogui_has_a_headed_browser(self): """PyAutoGUI requires a headed browser so that it can focus on the correct element when performing actions.""" @@ -1039,6 +1043,8 @@ def __install_pyautogui_if_missing(self): constants.PipInstall.FINDLOCK ) with pip_find_lock: # Prevent issues with multiple processes + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) try: import pyautogui with suppress(Exception): @@ -1124,6 +1130,7 @@ def gui_press_key(self, key): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: + self.__make_sure_pyautogui_lock_is_writable() pyautogui.press(key) time.sleep(0.044) self.__slow_mode_pause_if_set() @@ -1137,6 +1144,7 @@ def gui_press_keys(self, keys): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: + self.__make_sure_pyautogui_lock_is_writable() for key in keys: pyautogui.press(key) time.sleep(0.044) @@ -1151,6 +1159,7 @@ def gui_write(self, text): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: + self.__make_sure_pyautogui_lock_is_writable() pyautogui.write(text) self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait()) @@ -1171,6 +1180,7 @@ def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: # Prevent issues with multiple processes + self.__make_sure_pyautogui_lock_is_writable() pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) if timeframe >= 0.25: time.sleep(0.056) # Wait if moving at human-speed @@ -1191,6 +1201,7 @@ def gui_click_x_y(self, x, y, timeframe=0.25): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: # Prevent issues with multiple processes + self.__make_sure_pyautogui_lock_is_writable() self.__install_pyautogui_if_missing() import pyautogui pyautogui = self.__get_configured_pyautogui(pyautogui) @@ -1408,6 +1419,7 @@ def gui_hover_and_click(self, hover_selector, click_selector): constants.MultiBrowser.PYAUTOGUILOCK ) with gui_lock: + self.__make_sure_pyautogui_lock_is_writable() self.bring_active_window_to_front() self.gui_hover_element(hover_selector) time.sleep(0.15) diff --git a/seleniumbase/core/style_sheet.py b/seleniumbase/core/style_sheet.py index 7a46a5b8ce1..8414ae62f8a 100644 --- a/seleniumbase/core/style_sheet.py +++ b/seleniumbase/core/style_sheet.py @@ -1,4 +1,5 @@ import sys +import textwrap from seleniumbase.fixtures import constants @@ -116,6 +117,7 @@ def get_report_style(): } """ ) + style = textwrap.dedent(style) Saved.report_style = style return style @@ -132,6 +134,7 @@ def get_bt_backdrop_style(): box-shadow: 0 0 0 88422px rgba(0, 0, 0, 0.42); pointer-events: auto !important; }""" + bt_backdrop_style = textwrap.dedent(bt_backdrop_style) Saved.bt_backdrop_style = bt_backdrop_style return bt_backdrop_style @@ -150,6 +153,7 @@ def get_dt_backdrop_style(): button.driver-prev-btn.driver-disabled { visibility: hidden; }""" + dt_backdrop_style = textwrap.dedent(dt_backdrop_style) Saved.dt_backdrop_style = dt_backdrop_style return dt_backdrop_style @@ -167,6 +171,7 @@ def get_messenger_style(): box-shadow: 2px 2px 9px 4px rgba(32, 142, 120, 0.28), 2px 2px 9px 4px rgba(200, 240, 80, 0.34) !important; }""" % font_family + messenger_style = textwrap.dedent(messenger_style) Saved.messenger_style = messenger_style return messenger_style @@ -181,6 +186,7 @@ def get_sh_style_test(): scrollTo: true } });""" + sh_style_test = textwrap.dedent(sh_style_test) Saved.sh_style_test = sh_style_test return sh_style_test @@ -193,6 +199,7 @@ def get_hops_backdrop_style(): .hopscotch-bubble-container { font-size: 110%; }""" + hops_backdrop_style = textwrap.dedent(hops_backdrop_style) Saved.hops_backdrop_style = hops_backdrop_style return hops_backdrop_style @@ -227,6 +234,7 @@ def get_introjs_style(): box-sizing: content-box; position: absolute; }""" + introjs_style = textwrap.dedent(introjs_style) Saved.introjs_style = introjs_style return introjs_style @@ -261,6 +269,7 @@ def get_sh_backdrop_style(): body.shepherd-active { pointer-events: none !important; }""" + sh_backdrop_style = textwrap.dedent(sh_backdrop_style) Saved.sh_backdrop_style = sh_backdrop_style return sh_backdrop_style @@ -387,5 +396,6 @@ def get_pytest_style(): .desc.active .sort-icon { border-top: 8px solid #999; }""" + pytest_style = textwrap.dedent(pytest_style) Saved.pytest_style = pytest_style return pytest_style diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index e2322b9bce8..d31abe9cd22 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -7042,6 +7042,8 @@ def get_pdf_text( constants.PipInstall.FINDLOCK ) with pip_find_lock: + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) if sys.version_info < (3, 9): # Fix bug in newer cryptography for Python 3.7 and 3.8: # "pyo3_runtime.PanicException: Python API call failed" @@ -7292,6 +7294,8 @@ def save_element_as_image_file( constants.PipInstall.FINDLOCK ) with pip_find_lock: + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) try: from PIL import Image, ImageDraw except Exception: @@ -7337,6 +7341,10 @@ def download_file(self, file_url, destination_folder=None): constants.MultiBrowser.DOWNLOAD_FILE_LOCK ) with download_file_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DOWNLOAD_FILE_LOCK + ) if not destination_folder: destination_folder = constants.Files.DOWNLOADS_FOLDER if not os.path.exists(destination_folder): @@ -7357,6 +7365,10 @@ def save_file_as(self, file_url, new_file_name, destination_folder=None): constants.MultiBrowser.DOWNLOAD_FILE_LOCK ) with download_file_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.MultiBrowser.DOWNLOAD_FILE_LOCK + ) if not destination_folder: destination_folder = constants.Files.DOWNLOADS_FOLDER if not os.path.exists(destination_folder): @@ -7462,6 +7474,8 @@ def get_data_from_downloaded_file(self, file, timeout=None, browser=False): constants.MultiBrowser.FILE_IO_LOCK ) with file_io_lock: + with suppress(Exception): + shared_utils.make_writable(constants.MultiBrowser.FILE_IO_LOCK) with open(fpath, "r") as f: data = f.read().strip() return data @@ -14057,19 +14071,12 @@ def __activate_virtual_display_as_needed(self): with pip_find_lock: pass except Exception: - # Need write permissions - with suppress(Exception): - mode = os.stat(constants.PipInstall.FINDLOCK).st_mode - mode |= (mode & 0o444) >> 1 # copy R bits to W - os.chmod(constants.PipInstall.FINDLOCK, mode) - try: - with pip_find_lock: - pass - except Exception: - # Since missing permissions, skip the locks - self.__activate_virtual_display() - return + # Since missing permissions, skip the locks + self.__activate_virtual_display() + return with pip_find_lock: # Prevent issues with multiple processes + with suppress(Exception): + shared_utils.make_writable(constants.PipInstall.FINDLOCK) self.__activate_virtual_display() def __ad_block_as_needed(self): @@ -15091,6 +15098,10 @@ def setUp(self, masterqa_mode=False): if self.dashboard: if self._multithreaded: with self.dash_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.Dashboard.LOCKFILE + ) if not self._dash_initialized: sb_config._dashboard_initialized = True self._dash_initialized = True @@ -15650,6 +15661,8 @@ def _process_dashboard_entry(self, has_exception, init=False): constants.Dashboard.LOCKFILE ) with self.dash_lock: + with suppress(Exception): + shared_utils.make_writable(constants.Dashboard.LOCKFILE) self.__process_dashboard(has_exception, init) else: self.__process_dashboard(has_exception, init) @@ -16419,6 +16432,10 @@ def tearDown(self): if self.dashboard: if self._multithreaded: with self.dash_lock: + with suppress(Exception): + shared_utils.make_writable( + constants.Dashboard.LOCKFILE + ) self.__process_dashboard(has_exception) else: self.__process_dashboard(has_exception) diff --git a/seleniumbase/fixtures/shared_utils.py b/seleniumbase/fixtures/shared_utils.py index d8368828e0b..9ca88a180cd 100644 --- a/seleniumbase/fixtures/shared_utils.py +++ b/seleniumbase/fixtures/shared_utils.py @@ -128,6 +128,20 @@ def is_chrome_130_or_newer(self, binary_location=None): return False +def make_writable(file_path): + # Set permissions to: "If you can read it, you can write it." + mode = os.stat(file_path).st_mode + mode |= (mode & 0o444) >> 1 # copy R bits to W + os.chmod(file_path, mode) + + +def make_executable(file_path): + # Set permissions to: "If you can read it, you can execute it." + mode = os.stat(file_path).st_mode + mode |= (mode & 0o444) >> 2 # copy R bits to X + os.chmod(file_path, mode) + + def format_exc(exception, message): """Formats an exception message to make the output cleaner.""" from selenium.common.exceptions import ElementNotVisibleException diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 9220ca5072b..3846ee3ca98 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -2209,6 +2209,20 @@ def _perform_pytest_unconfigure_(config): f.write(the_html_r) # Finalize the HTML report with open(html_report_path_copy, "w", encoding="utf-8") as f: f.write(the_html_r) # Finalize the HTML report + assets_style = "./assets/style.css" + if os.path.exists(assets_style): + html_style = None + with open(assets_style, "r", encoding="utf-8") as f: + html_style = f.read() + if html_style: + html_style = html_style.replace("top: -50px;", "top: 2px;") + html_style = html_style.replace("+ 50px)", "+ 40px)") + html_style = html_style.replace("ht: 240px;", "ht: 228px;") + html_style = html_style.replace( + "- 80px);", "- 80px);\n margin-bottom: -42px;" + ) + with open(assets_style, "w", encoding="utf-8") as f: + f.write(html_style) # Done with "pytest_unconfigure" unless using the Dashboard return stamp = "" @@ -2290,6 +2304,20 @@ def _perform_pytest_unconfigure_(config): ) with open(dashboard_path, "w", encoding="utf-8") as f: f.write(the_html_d) # Finalize the dashboard + assets_style = "./assets/style.css" + if os.path.exists(assets_style): + html_style = None + with open(assets_style, "r", encoding="utf-8") as f: + html_style = f.read() + if html_style: + html_style = html_style.replace("top: -50px;", "top: 2px;") + html_style = html_style.replace("+ 50px)", "+ 40px)") + html_style = html_style.replace("ht: 240px;", "ht: 228px;") + html_style = html_style.replace( + "- 80px);", "- 80px);\n margin-bottom: -42px;" + ) + with open(assets_style, "w", encoding="utf-8") as f: + f.write(html_style) # Part 2: Appending a pytest html report with dashboard data html_report_path = None if sb_config._html_report_name: diff --git a/setup.py b/setup.py index 52b4cbe594b..371dbc08124 100755 --- a/setup.py +++ b/setup.py @@ -193,7 +193,7 @@ 'iniconfig==2.0.0', 'pluggy==1.5.0', 'pytest==8.3.4', - "pytest-html==4.0.2", + "pytest-html==4.0.2", # Newer ones had issues 'pytest-metadata==3.1.1', "pytest-ordering==0.6", 'pytest-rerunfailures==14.0;python_version<"3.9"', @@ -222,7 +222,7 @@ # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ 'coverage>=7.6.1;python_version<"3.9"', - 'coverage>=7.6.8;python_version>="3.9"', + 'coverage>=7.6.9;python_version>="3.9"', 'pytest-cov>=5.0.0;python_version<"3.9"', 'pytest-cov>=6.0.0;python_version>="3.9"', ],