diff --git a/.gitignore b/.gitignore index 7aec1e4d7ce..86fc05b17c1 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ latest_report report_archives archived_reports html_report.html +last_report.html report.html report.xml diff --git a/requirements.txt b/requirements.txt index 1c9f1bcd2fe..cc26b99f660 100755 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,7 @@ execnet==2.1.1 iniconfig==2.0.0 pluggy==1.5.0 pytest==8.3.4 -pytest-html==4.1.1 +pytest-html==4.0.2 pytest-metadata==3.1.1 pytest-ordering==0.6 pytest-rerunfailures==14.0;python_version<"3.9" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 773faecf514..093f5cfc1fc 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.33.4" +__version__ = "4.33.5" diff --git a/seleniumbase/console_scripts/sb_mkdir.py b/seleniumbase/console_scripts/sb_mkdir.py index 585ce88cd6f..863a4979f5c 100644 --- a/seleniumbase/console_scripts/sb_mkdir.py +++ b/seleniumbase/console_scripts/sb_mkdir.py @@ -259,6 +259,7 @@ def main(): data.append("report_archives") data.append("archived_reports") data.append("html_report.html") + data.append("last_report.html") data.append("report.html") data.append("report.xml") data.append("dashboard.html") diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 9398b9ab19c..e4aad9e0f0b 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -817,6 +817,63 @@ def verify_pyautogui_has_a_headed_browser(driver): ) +def __install_pyautogui_if_missing(): + try: + import pyautogui + with suppress(Exception): + use_pyautogui_ver = constants.PyAutoGUI.VER + if pyautogui.__version__ != use_pyautogui_ver: + del pyautogui + shared_utils.pip_install( + "pyautogui", version=use_pyautogui_ver + ) + import pyautogui + except Exception: + print("\nPyAutoGUI required! Installing now...") + shared_utils.pip_install( + "pyautogui", version=constants.PyAutoGUI.VER + ) + try: + import pyautogui + except Exception: + if ( + IS_LINUX + and hasattr(sb_config, "xvfb") + and hasattr(sb_config, "headed") + and hasattr(sb_config, "headless") + and hasattr(sb_config, "headless2") + and (not sb_config.headed or sb_config.xvfb) + and not (sb_config.headless or sb_config.headless2) + ): + from sbvirtualdisplay import Display + 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=(xvfb_width, xvfb_height), + backend="xvfb", + use_xauth=True, + ) + xvfb_display.start() + + def install_pyautogui_if_missing(driver): verify_pyautogui_has_a_headed_browser(driver) pip_find_lock = fasteners.InterProcessLock( @@ -829,61 +886,15 @@ def install_pyautogui_if_missing(driver): # Need write permissions with suppress(Exception): make_writable(constants.PipInstall.FINDLOCK) - with pip_find_lock: # Prevent issues with multiple processes try: - import pyautogui - with suppress(Exception): - use_pyautogui_ver = constants.PyAutoGUI.VER - if pyautogui.__version__ != use_pyautogui_ver: - del pyautogui - shared_utils.pip_install( - "pyautogui", version=use_pyautogui_ver - ) - import pyautogui + with pip_find_lock: + pass except Exception: - print("\nPyAutoGUI required! Installing now...") - shared_utils.pip_install( - "pyautogui", version=constants.PyAutoGUI.VER - ) - try: - import pyautogui - except Exception: - if ( - IS_LINUX - and hasattr(sb_config, "xvfb") - and hasattr(sb_config, "headed") - and hasattr(sb_config, "headless") - and hasattr(sb_config, "headless2") - and (not sb_config.headed or sb_config.xvfb) - and not (sb_config.headless or sb_config.headless2) - ): - from sbvirtualdisplay import Display - 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=(xvfb_width, xvfb_height), - backend="xvfb", - use_xauth=True, - ) - xvfb_display.start() + # Since missing permissions, skip the locks + __install_pyautogui_if_missing() + return + with pip_find_lock: # Prevent issues with multiple processes + __install_pyautogui_if_missing() def get_configured_pyautogui(pyautogui_copy): diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index e1522530a67..e2322b9bce8 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -13974,12 +13974,82 @@ def __activate_standard_virtual_display(self): self.headless_active = True sb_config.headless_active = True + def __activate_virtual_display(self): + if self.undetectable and not (self.headless or self.headless2): + from sbvirtualdisplay import Display + 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=(self._xvfb_width, self._xvfb_height), + backend="xvfb", + use_xauth=True, + ) + self._xvfb_display.start() + if "DISPLAY" not in os.environ.keys(): + print( + "\nX11 display failed! Will use regular xvfb!" + ) + self.__activate_standard_virtual_display() + except Exception as e: + if hasattr(e, "msg"): + print("\n" + str(e.msg)) + else: + print(e) + print("\nX11 display failed! Will use regular xvfb!") + self.__activate_standard_virtual_display() + return + pyautogui_is_installed = False + try: + import pyautogui + with suppress(Exception): + use_pyautogui_ver = constants.PyAutoGUI.VER + if pyautogui.__version__ != use_pyautogui_ver: + del pyautogui # To get newer ver + shared_utils.pip_install( + "pyautogui", version=use_pyautogui_ver + ) + import pyautogui + pyautogui_is_installed = True + except Exception: + message = ( + "PyAutoGUI is required for UC Mode on Linux! " + "Installing now..." + ) + print("\n" + message) + shared_utils.pip_install( + "pyautogui", version=constants.PyAutoGUI.VER + ) + import pyautogui + pyautogui_is_installed = True + if ( + pyautogui_is_installed + and hasattr(pyautogui, "_pyautogui_x11") + ): + try: + pyautogui._pyautogui_x11._display = ( + Xlib.display.Display(os.environ['DISPLAY']) + ) + sb_config._pyautogui_x11_display = ( + pyautogui._pyautogui_x11._display + ) + except Exception as e: + if hasattr(e, "msg"): + print("\n" + str(e.msg)) + else: + print(e) + else: + self.__activate_standard_virtual_display() + def __activate_virtual_display_as_needed(self): """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): - from sbvirtualdisplay import Display pip_find_lock = fasteners.InterProcessLock( constants.PipInstall.FINDLOCK ) @@ -13992,75 +14062,15 @@ def __activate_virtual_display_as_needed(self): 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 with pip_find_lock: # Prevent issues with multiple processes - 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=(self._xvfb_width, self._xvfb_height), - backend="xvfb", - use_xauth=True, - ) - self._xvfb_display.start() - if "DISPLAY" not in os.environ.keys(): - print( - "\nX11 display failed! Will use regular xvfb!" - ) - self.__activate_standard_virtual_display() - except Exception as e: - if hasattr(e, "msg"): - print("\n" + str(e.msg)) - else: - print(e) - print("\nX11 display failed! Will use regular xvfb!") - self.__activate_standard_virtual_display() - return - pyautogui_is_installed = False - try: - import pyautogui - with suppress(Exception): - use_pyautogui_ver = constants.PyAutoGUI.VER - if pyautogui.__version__ != use_pyautogui_ver: - del pyautogui # To get newer ver - shared_utils.pip_install( - "pyautogui", version=use_pyautogui_ver - ) - import pyautogui - pyautogui_is_installed = True - except Exception: - message = ( - "PyAutoGUI is required for UC Mode on Linux! " - "Installing now..." - ) - print("\n" + message) - shared_utils.pip_install( - "pyautogui", version=constants.PyAutoGUI.VER - ) - import pyautogui - pyautogui_is_installed = True - if ( - pyautogui_is_installed - and hasattr(pyautogui, "_pyautogui_x11") - ): - try: - pyautogui._pyautogui_x11._display = ( - Xlib.display.Display(os.environ['DISPLAY']) - ) - sb_config._pyautogui_x11_display = ( - pyautogui._pyautogui_x11._display - ) - except Exception as e: - if hasattr(e, "msg"): - print("\n" + str(e.msg)) - else: - print(e) - else: - self.__activate_standard_virtual_display() + self.__activate_virtual_display() def __ad_block_as_needed(self): """This is an internal method for handling ad-blocking. diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 6f4e8dd1b04..9220ca5072b 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1709,6 +1709,7 @@ def pytest_configure(config): sb_config._saved_dashboard_pie = None # Copy of pie chart for html report sb_config._dash_final_summary = None # Dash status to add to html report sb_config._html_report_name = None # The name of the pytest html report + sb_config._html_report_copy = None # The copy of the pytest html report arg_join = " ".join(sys_argv) if ( @@ -1742,6 +1743,7 @@ def pytest_configure(config): if sb_config.dashboard: if sb_config._html_report_name == "dashboard.html": sb_config._dash_is_html_report = True + sb_config._html_report_copy = "last_report.html" # Recorder Mode does not support multi-threaded / multi-process runs. if sb_config.recorder_mode and sb_config._multithreaded: @@ -2151,6 +2153,10 @@ def _perform_pytest_unconfigure_(config): html_report_path = os.path.join( abs_path, sb_config._html_report_name ) + if sb_config._html_report_copy: + html_report_path_copy = os.path.join( + abs_path, sb_config._html_report_copy + ) if ( sb_config._using_html_report and html_report_path @@ -2201,6 +2207,8 @@ def _perform_pytest_unconfigure_(config): ) with open(html_report_path, "w", encoding="utf-8") as f: 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 # Done with "pytest_unconfigure" unless using the Dashboard return stamp = "" @@ -2288,6 +2296,10 @@ def _perform_pytest_unconfigure_(config): html_report_path = os.path.join( abs_path, sb_config._html_report_name ) + if sb_config._html_report_copy: + html_report_path_copy = os.path.join( + abs_path, sb_config._html_report_copy + ) if ( sb_config._using_html_report and html_report_path @@ -2358,6 +2370,8 @@ def _perform_pytest_unconfigure_(config): ) with open(html_report_path, "w", encoding="utf-8") as f: 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 except KeyboardInterrupt: pass except Exception: diff --git a/setup.py b/setup.py index 72e45543f0c..52b4cbe594b 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.1.1", + "pytest-html==4.0.2", 'pytest-metadata==3.1.1', "pytest-ordering==0.6", 'pytest-rerunfailures==14.0;python_version<"3.9"',