From 951b57c6be8b947d5b743b6649aea539ec86930d Mon Sep 17 00:00:00 2001 From: Taka Date: Fri, 29 Jan 2021 13:23:50 +1100 Subject: [PATCH] Updated to Python 3.8.7. --- .pylintrc | 6 ++-- Dockerfile | 4 +-- conftest.py | 36 ++++++++++++------- freeze.py | 6 ++-- requirements-dev.txt | 14 ++++---- requirements.txt | 10 +++--- setup.py | 5 +-- tools/build_file.py | 2 +- vasl_templates/main.py | 7 ++-- vasl_templates/main_window.py | 5 ++- .../tools/dump_log_file_analysis.py | 4 +-- vasl_templates/webapp/scenarios.py | 2 +- vasl_templates/webapp/static/help/index.html | 5 +-- vasl_templates/webapp/tests/__init__.py | 3 ++ .../webapp/tests/test_capabilities.py | 13 +++---- vasl_templates/webapp/tests/test_counters.py | 3 +- .../tests/test_dirty_scenario_checks.py | 4 +-- vasl_templates/webapp/tests/test_jshint.py | 3 +- .../webapp/tests/test_scenario_search.py | 3 +- vasl_templates/webapp/tests/test_snippets.py | 3 +- vasl_templates/webapp/tests/test_vassal.py | 5 ++- .../webapp/tests/test_vehicles_ordnance.py | 5 +-- vasl_templates/webapp/tests/test_vo_notes.py | 3 +- .../webapp/tests/test_vo_reports.py | 3 +- vasl_templates/webapp/tests/utils.py | 7 ++-- vasl_templates/webapp/utils.py | 8 ++--- vasl_templates/webapp/vassal.py | 2 +- vasl_templates/webapp/webdriver.py | 27 +++++++------- 28 files changed, 114 insertions(+), 84 deletions(-) diff --git a/.pylintrc b/.pylintrc index 00856c6..0678224 100644 --- a/.pylintrc +++ b/.pylintrc @@ -144,7 +144,9 @@ disable=print-statement, duplicate-code, # can't get it to shut up about @pytest.mark.skipif's :-/ no-else-return, len-as-condition, - consider-using-enumerate + consider-using-enumerate, + import-outside-toplevel, + isinstance-second-argument-not-valid-type # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -511,7 +513,7 @@ max-public-methods=20 max-returns=10 # Maximum number of statements in function / method body -max-statements=80 +max-statements=100 # Minimum number of public methods for a class (see R0903). min-public-methods=2 diff --git a/Dockerfile b/Dockerfile index 0221ac4..9c1cd2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ FROM centos:8 AS base RUN dnf -y upgrade-minimal # install Python -RUN dnf install -y python36 && pip3 install --upgrade pip +RUN dnf install -y python38 python3-pip # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -21,7 +21,7 @@ RUN dnf install -y python36 && pip3 install --upgrade pip FROM base AS build # set up a virtualenv -RUN python3 -m venv /opt/venv && pip3 install --upgrade pip +RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" # install the application requirements diff --git a/conftest.py b/conftest.py index d0170da..2587538 100644 --- a/conftest.py +++ b/conftest.py @@ -18,6 +18,8 @@ from vasl_templates.webapp.tests.control_tests import ControlTests FLASK_WEBAPP_PORT = 5011 +_pytest_options = None + # --------------------------------------------------------------------- def pytest_addoption( parser ): @@ -59,6 +61,15 @@ def pytest_addoption( parser ): help="Use the clipboard to get snippets." ) +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def pytest_configure( config ): + """Called after command-line options have been parsed.""" + global _pytest_options + _pytest_options = config.option + import vasl_templates.webapp.tests + vasl_templates.webapp.tests.pytest_options = config.option + # --------------------------------------------------------------------- _webapp = None @@ -85,7 +96,7 @@ def _make_webapp(): """Create the global webapp fixture.""" # initialize - webapp_url = pytest.config.option.webapp_url #pylint: disable=no-member + webapp_url = _pytest_options.webapp_url if webapp_url and not webapp_url.startswith( "http://" ): webapp_url = "http://" + webapp_url app.base_url = webapp_url if webapp_url else "http://localhost:{}".format( FLASK_WEBAPP_PORT ) @@ -103,11 +114,11 @@ def _make_webapp(): # stop the browser from checking for a dirty scenario when leaving the page kwargs["disable_close_window_check"] = 1 # check if the tests are being run headless - if pytest.config.option.headless: #pylint: disable=no-member + if _pytest_options.headless: # yup - there is no clipboard support :-/ - pytest.config.option.use_clipboard = False #pylint: disable=no-member + _pytest_options.use_clipboard = False # check if we should disable using the clipboard for snippets - if not pytest.config.option.use_clipboard: #pylint: disable=no-member + if not _pytest_options.use_clipboard: # NOTE: It's not a bad idea to bypass the clipboard, even when running in a browser, # to avoid problems if something else uses the clipboard while the tests are running. kwargs["store_clipboard"] = 1 @@ -152,7 +163,7 @@ def _make_webapp(): ) except urllib.error.HTTPError as ex: if ex.code == 404: - raise RuntimeError( "Can't get the test control port - has remote test control been enabled?" ) + raise RuntimeError( "Can't get the test control port - has remote test control been enabled?" ) from ex raise port_no = resp.get( "port" ) if not port_no: @@ -188,20 +199,21 @@ def webdriver( request ): from selenium import webdriver as wb if driver == "firefox": options = wb.FirefoxOptions() - options.set_headless( headless = pytest.config.option.headless ) #pylint: disable=no-member + options.headless = _pytest_options.headless driver = wb.Firefox( - firefox_options = options, - log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" ) + options = options, + service_log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" ) ) elif driver == "chrome": options = wb.ChromeOptions() - options.set_headless( headless = pytest.config.option.headless ) #pylint: disable=no-member - driver = wb.Chrome( chrome_options=options ) + options.headless = _pytest_options.headless + options.add_argument( "--disable-gpu" ) + driver = wb.Chrome( options=options ) elif driver == "ie": # NOTE: IE11 requires a registry key to be set: # https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver#required-configuration options = wb.IeOptions() - if pytest.config.option.headless: #pylint: disable=no-member + if _pytest_options.headless: raise RuntimeError( "IE WebDriver cannot be run headless." ) options.IntroduceInstabilityByIgnoringProtectedModeSettings = True options.EnsureCleanSession = True @@ -210,7 +222,7 @@ def webdriver( request ): raise RuntimeError( "Unknown webdriver: {}".format( driver ) ) # set the browser size - words = pytest.config.option.window_size.split( "x" ) #pylint: disable=no-member + words = _pytest_options.window_size.split( "x" ) driver.set_window_size( int(words[0]), int(words[1]) ) # return the webdriver to the caller diff --git a/freeze.py b/freeze.py index 01d4e0a..a7844ae 100755 --- a/freeze.py +++ b/freeze.py @@ -32,7 +32,8 @@ def get_git_info(): # get the latest commit ID proc = subprocess.run( [ "git", "log" ], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", + check=True ) buf = proc.stdout.split( "\n" )[0] mo = re.search( r"^commit ([a-z0-9]+)$", buf ) @@ -41,7 +42,8 @@ def get_git_info(): # get the current git branch proc = subprocess.run( [ "git", "branch" ], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", + check=True ) lines = [ s for s in proc.stdout.split("\n") if s.startswith("* ") ] if len(lines) != 1: diff --git a/requirements-dev.txt b/requirements-dev.txt index 346de95..3251b80 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ -pytest==3.6.0 -grpcio-tools==1.33.2 -tabulate==0.8.2 -lxml==4.2.4 -pylint==1.9.2 -pytest-pylint==0.9.0 -pyinstaller==3.6 +pytest==6.2.1 +grpcio-tools==1.34.1 +tabulate==0.8.7 +lxml==4.6.2 +pylint==2.6.0 +pytest-pylint==0.18.0 +pyinstaller==4.2 diff --git a/requirements.txt b/requirements.txt index 7dc6424..fc0955d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -# python 3.6.8 +# python 3.8.7 -flask==1.0.2 +flask==1.1.2 pyyaml==5.3.1 -pillow==7.0.0 -selenium==3.12.0 -click==6.7 +pillow==8.1.0 +selenium==3.141.0 +click==7.1.2 diff --git a/setup.py b/setup.py index 694e581..2b95d5c 100644 --- a/setup.py +++ b/setup.py @@ -37,8 +37,9 @@ setup( "gui": [ # NOTE: PyQt5 requirements: https://doc.qt.io/qt-5/linux.html # Linux: mesa-libGL-devel ; @"C Development Tools and Libraries" - # nb: WebEngine seems to be broken in 5.10.1 :-/ - "PyQT5==5.10.0", + # NOTE: You may need to disable VMware 3D acceleration, if QWebEngineView is crashing. + "PyQT5==5.15.2", + "PyQtWebEngine==5.15.2", ], "dev": parse_requirements( "requirements-dev.txt" ), }, diff --git a/tools/build_file.py b/tools/build_file.py index 670081f..423fef0 100755 --- a/tools/build_file.py +++ b/tools/build_file.py @@ -46,7 +46,7 @@ class BuildFile: val = click.style( child.tag, fg="green" ) if line_nos: val += ":{}".format( click.style( str(child.sourceline), fg="cyan" ) ) - click.echo( "{} {} {}".format( header, val, header ) ) + click.echo( "{header} {} {header}".format( val, header=header ) ) click.echo() # dump any attributes if attribs: diff --git a/vasl_templates/main.py b/vasl_templates/main.py index 41e0191..453f096 100755 --- a/vasl_templates/main.py +++ b/vasl_templates/main.py @@ -179,10 +179,9 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin raise SimpleError( "Unexpected server check response: {}".format( resp ) ) if resp[6:] == INSTANCE_ID: break - else: - from vasl_templates.webapp.config.constants import APP_NAME - QMessageBox.warning( None, APP_NAME, "The program is already running." ) - return -1 + from vasl_templates.webapp.config.constants import APP_NAME + QMessageBox.warning( None, APP_NAME, "The program is already running." ) + return -1 except URLError: # no response - the webapp server is probably still starting up time.sleep( 0.25 ) diff --git a/vasl_templates/main_window.py b/vasl_templates/main_window.py index cb76e30..63ba574 100644 --- a/vasl_templates/main_window.py +++ b/vasl_templates/main_window.py @@ -97,7 +97,7 @@ class MainWindow( QWidget ): # initialize the layout layout = QVBoxLayout( self ) - layout.addWidget( menu_bar ) + layout.setMenuBar( menu_bar ) # FUDGE! We offer the option to disable the QWebEngineView since getting it to run # under Windows (especially older versions) is unreliable (since it uses OpenGL). # By disabling it, the program will at least start (in particular, the webapp server), @@ -165,6 +165,9 @@ class MainWindow( QWidget ): if self._view: app_settings.setValue( "MainWindow/geometry", self.saveGeometry() ) self.close() + # FUDGE! We need to do this to stop PyQt 5.15.2 from complaining that the profile + # is being deleted while the page is still alive. + self._view.page().deleteLater() # check if the scenario is dirty def callback( is_dirty ): diff --git a/vasl_templates/tools/dump_log_file_analysis.py b/vasl_templates/tools/dump_log_file_analysis.py index fd6786c..a6067c7 100755 --- a/vasl_templates/tools/dump_log_file_analysis.py +++ b/vasl_templates/tools/dump_log_file_analysis.py @@ -179,7 +179,7 @@ def dump_time_plot( players, log_file, roll_type, window_size ): """Dump the buffered ROLL events.""" print( tabulate.tabulate( rolls, tablefmt="plain" ) ) - def onTurnTrack( evt ): #pylint: disable=unused-variable + def onTurnTrack( evt ): #pylint: disable=unused-variable,possibly-unused-variable """Process a TURN TRACK event.""" nonlocal rolls if rolls: @@ -189,7 +189,7 @@ def dump_time_plot( players, log_file, roll_type, window_size ): print( "--- {} Turn {} {} ---".format( evt["side"], evt["turnNo"], evt["phase"] ) ) print() - def onRoll( evt ) : #pylint: disable=unused-variable + def onRoll( evt ) : #pylint: disable=unused-variable,possibly-unused-variable """Process a ROLL event""" # check if we should process this ROLL event if roll_type: diff --git a/vasl_templates/webapp/scenarios.py b/vasl_templates/webapp/scenarios.py index 8373d6e..4706e52 100644 --- a/vasl_templates/webapp/scenarios.py +++ b/vasl_templates/webapp/scenarios.py @@ -385,7 +385,7 @@ def get_scenario_card( scenario_id ): #pylint: disable=too-many-branches min_turns = scenario.get( "min_turns", "0" ) max_turns = scenario.get( "max_turns", "0" ) if min_turns != "0": - if min_turns == max_turns or max_turns == "0": + if max_turns in ("0",min_turns): args[ "TURN_COUNT" ] = friendly_fractions( min_turns, "turn", "turns" ) elif max_turns != "0": args[ "TURN_COUNT" ] = "{}-{} turns".format( friendly_fractions(min_turns), friendly_fractions(max_turns) ) diff --git a/vasl_templates/webapp/static/help/index.html b/vasl_templates/webapp/static/help/index.html index 217bb4f..863ad9f 100644 --- a/vasl_templates/webapp/static/help/index.html +++ b/vasl_templates/webapp/static/help/index.html @@ -52,7 +52,7 @@

If you're on a Mac or Linux, you can run the program directly from the source code. Get a copy from Github in the usual way, by git clone'ing it, or downloading a ZIP and unpacking it somewhere. -

The web server was written and tested using Python 3.6, but it doesn't do anything particularly funky, so any recent version of Python should work. +

The web server was written and tested using Python 3.8.7, but it doesn't do anything particularly funky, so any recent version of Python should work.

While not essential, it is strongly recommended that you set up a virtual environment first. Then, install the requirements:

@@ -63,7 +63,8 @@ pip install .[gui]

If you're on Windows, the Qt runtime will have been installed as part of PyQt5 (when you did the pip install above), but if you're in a virtual environment and you're getting "DLL load failed" errors, this is due to a problem with the way Python sets up the virtualenv. In the virtualenv's scripts/ sub-directory, there should be two Python DLL's, so if you're missing python3.dll, copy it over from the Python installation the virtualenv was created from, and you should be good to go. -

If you're on Linux, you will need to install Qt 5.10.0. While your distro may have it as a package, I didn't have much luck on Fedora 27, and had to install it manually using their installer. +

If you're on Linux, you may need to install Qt 5.15.2. On Fedora 33, running the "gui" install above should install everything you need. +

Then, just run the vasl-templates command.

Running just the web server

diff --git a/vasl_templates/webapp/tests/__init__.py b/vasl_templates/webapp/tests/__init__.py index e69de29..7d76cd6 100644 --- a/vasl_templates/webapp/tests/__init__.py +++ b/vasl_templates/webapp/tests/__init__.py @@ -0,0 +1,3 @@ +"""Module definitions.""" + +pytest_options = None diff --git a/vasl_templates/webapp/tests/test_capabilities.py b/vasl_templates/webapp/tests/test_capabilities.py index 1fb0f4f..c98b02c 100644 --- a/vasl_templates/webapp/tests/test_capabilities.py +++ b/vasl_templates/webapp/tests/test_capabilities.py @@ -10,6 +10,7 @@ from vasl_templates.webapp.tests.utils import \ init_webapp, select_menu_option, select_tab, click_dialog_button, \ find_child, find_children, wait_for_clipboard, \ set_scenario_date +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.test_vo_reports import get_vo_report from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario @@ -18,7 +19,7 @@ _IGNORE_CAPABILITIES = [ "T", "NT", "ST" ] # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_month_capabilities( webapp, webdriver ): #pylint: disable=too-many-statements """Test date-based capabilities that change in the middle of a year.""" @@ -302,7 +303,7 @@ def test_month_capabilities( webapp, webdriver ): #pylint: disable=too-many-stat # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_kfw( webapp, webdriver ): """Test date-based capabilities for K:FW vehicles/ordnance.""" @@ -322,7 +323,7 @@ def test_kfw( webapp, webdriver ): # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_theater_capabilities( webapp, webdriver ): """Test theater-specific capabilities.""" @@ -398,7 +399,7 @@ def test_theater_capabilities( webapp, webdriver ): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_theater_capabilities_bfp( webapp, webdriver ): """Test theater-specific capabilities (BFP extension).""" @@ -425,7 +426,7 @@ def test_theater_capabilities_bfp( webapp, webdriver ): # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_american_ordnance_note_c( webapp, webdriver ): """Test handling of American Ordnance Note C.""" @@ -450,7 +451,7 @@ def test_american_ordnance_note_c( webapp, webdriver ): # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_nationality_capabilities( webapp, webdriver ): """Test nationality-specific capabilities.""" diff --git a/vasl_templates/webapp/tests/test_counters.py b/vasl_templates/webapp/tests/test_counters.py index 31c3263..cc85388 100644 --- a/vasl_templates/webapp/tests/test_counters.py +++ b/vasl_templates/webapp/tests/test_counters.py @@ -12,6 +12,7 @@ from vasl_templates.webapp.vassal import SUPPORTED_VASSAL_VERSIONS from vasl_templates.webapp.vasl_mod import get_vo_gpids, SUPPORTED_VASL_MOD_VERSIONS from vasl_templates.webapp.vo import _kfw_listings #pylint: disable=protected-access from vasl_templates.webapp.utils import compare_version_strings +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.utils import init_webapp, select_tab, find_child, find_children from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario @@ -19,7 +20,7 @@ _EXPECTED_MISSING_GPIDS_EXCEPTIONS = [ "6.5.0", "6.5.1", "6.6.0", "6.6.1" ] # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_counter_images( webapp, webdriver ): #pylint: disable=too-many-locals """Test that counter images are served correctly.""" diff --git a/vasl_templates/webapp/tests/test_dirty_scenario_checks.py b/vasl_templates/webapp/tests/test_dirty_scenario_checks.py index a0087c2..b59a670 100644 --- a/vasl_templates/webapp/tests/test_dirty_scenario_checks.py +++ b/vasl_templates/webapp/tests/test_dirty_scenario_checks.py @@ -2,9 +2,9 @@ import re -import pytest from selenium.webdriver.support.ui import Select +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.test_scenario_persistence import ALL_SCENARIO_PARAMS from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.utils import \ @@ -174,5 +174,5 @@ def test_dirty_scenario_checks( webapp, webdriver ): for tab_id,params in ALL_SCENARIO_PARAMS.items(): for param in params: do_test( tab_id, param ) - if pytest.config.option.short_tests: #pylint: disable=no-member + if pytest_options.short_tests: break # nb: it's a bit excessive to check *every* parameter :-/ diff --git a/vasl_templates/webapp/tests/test_jshint.py b/vasl_templates/webapp/tests/test_jshint.py index aa4c241..86bc2b3 100644 --- a/vasl_templates/webapp/tests/test_jshint.py +++ b/vasl_templates/webapp/tests/test_jshint.py @@ -35,7 +35,8 @@ def test_jshint(): # run JSHint for the next file proc = subprocess.run( [ jshint, os.path.join(dname,fname) ], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", + check=False ) if proc.stdout or proc.stderr: print( "=== JSHint failed: {} ===".format( fname ) ) diff --git a/vasl_templates/webapp/tests/test_scenario_search.py b/vasl_templates/webapp/tests/test_scenario_search.py index 3528081..4537704 100644 --- a/vasl_templates/webapp/tests/test_scenario_search.py +++ b/vasl_templates/webapp/tests/test_scenario_search.py @@ -10,6 +10,7 @@ from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import StaleElementReferenceException +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario from vasl_templates.webapp.tests.test_vassal import run_vassal_tests from vasl_templates.webapp.tests.utils import init_webapp, select_tab, new_scenario, \ @@ -368,7 +369,7 @@ def test_roar_matching( webapp, webdriver ): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @pytest.mark.skipif( - pytest.config.option.webapp_url is not None, #pylint: disable=no-member + pytest_options.webapp_url is not None, reason = "Can't test against a remote webapp server." ) def test_roar_matching2( webapp, webdriver ): diff --git a/vasl_templates/webapp/tests/test_snippets.py b/vasl_templates/webapp/tests/test_snippets.py index 4f9178a..9e42f50 100644 --- a/vasl_templates/webapp/tests/test_snippets.py +++ b/vasl_templates/webapp/tests/test_snippets.py @@ -6,6 +6,7 @@ import pytest from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.utils import \ init_webapp, select_tab, find_snippet_buttons, set_template_params, wait_for, wait_for_clipboard, \ get_stored_msg, set_stored_msg_marker, find_child, find_children, adjust_html, \ @@ -313,7 +314,7 @@ def test_edit_templates( webapp, webdriver ): # However, the workaround we implemented in that script (dismissing the dialog) doesn't work here, # and I just couldn't get things to work (not even reloading the page each time helped) :-( @pytest.mark.skipif( - pytest.config.option.webdriver == "firefox", #pylint: disable=no-member + pytest_options.webdriver == "firefox", reason="Selenium problems (?) cause these tests to fail under Firefox." ) def test_snippet_images( webapp, webdriver ): diff --git a/vasl_templates/webapp/tests/test_vassal.py b/vasl_templates/webapp/tests/test_vassal.py index 7b87134..3a302ff 100644 --- a/vasl_templates/webapp/tests/test_vassal.py +++ b/vasl_templates/webapp/tests/test_vassal.py @@ -7,9 +7,8 @@ import base64 import random import typing.re #pylint: disable=import-error -import pytest - from vasl_templates.webapp.utils import TempFile, change_extn, compare_version_strings +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.utils import \ init_webapp, select_menu_option, get_stored_msg, set_stored_msg, set_stored_msg_marker, wait_for, \ new_scenario, set_player, find_child @@ -764,7 +763,7 @@ def run_vassal_tests( webapp, func, all_combos=None, min_vasl_version=None, vasl # check if we want to test all VASSAL+VASL combinations (nb: if not, we test against only one combination, # and since they all should give the same results, it doesn't matter which one. if all_combos is None: - all_combos = not pytest.config.option.short_tests #pylint: disable=no-member + all_combos = not pytest_options.short_tests if not all_combos: for _ in range(0,100): vasl_version = random.choice( vasl_versions ) diff --git a/vasl_templates/webapp/tests/test_vehicles_ordnance.py b/vasl_templates/webapp/tests/test_vehicles_ordnance.py index 4ad5dab..b4bff40 100644 --- a/vasl_templates/webapp/tests/test_vehicles_ordnance.py +++ b/vasl_templates/webapp/tests/test_vehicles_ordnance.py @@ -8,6 +8,7 @@ import pytest from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario, save_scenario from vasl_templates.webapp.tests.utils import \ init_webapp, get_nationalities, select_tab, set_template_params, find_child, find_children, \ @@ -342,7 +343,7 @@ def test_duplicate_vo_entries( webapp, webdriver ): # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_common_vo( webapp, webdriver ): #pylint: disable=too-many-locals """Test loading of common vehicles/ordnance and landing craft.""" @@ -415,7 +416,7 @@ def test_common_vo( webapp, webdriver ): #pylint: disable=too-many-locals if nat in ["thai","indonesian","anzac","burmese","filipino"]: # nb: these are in the BFP extension assert not elem.is_enabled() continue - elif nat == "kfw-cpva" and vo_type == "vehicles": + if nat == "kfw-cpva" and vo_type == "vehicles": assert not elem.is_enabled() continue elem.click() diff --git a/vasl_templates/webapp/tests/test_vo_notes.py b/vasl_templates/webapp/tests/test_vo_notes.py index 6b0fae5..53157da 100644 --- a/vasl_templates/webapp/tests/test_vo_notes.py +++ b/vasl_templates/webapp/tests/test_vo_notes.py @@ -10,6 +10,7 @@ import lxml.html import lxml.etree import tabulate +from vasl_templates.webapp.tests import pytest_options from vasl_templates.webapp.tests.utils import \ init_webapp, get_nationalities, select_tab, set_player, select_menu_option, click_dialog_button, \ find_child, find_children, wait_for, wait_for_clipboard @@ -431,7 +432,7 @@ def test_special_cases( webapp, webdriver ): # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_vo_notes_reports( webapp, webdriver ): #pylint: disable=too-many-locals """Check the vehicle/ordnance notes reports.""" diff --git a/vasl_templates/webapp/tests/test_vo_reports.py b/vasl_templates/webapp/tests/test_vo_reports.py index 96eb158..17b4eb6 100644 --- a/vasl_templates/webapp/tests/test_vo_reports.py +++ b/vasl_templates/webapp/tests/test_vo_reports.py @@ -10,12 +10,13 @@ import lxml.html import lxml.etree import tabulate +from vasl_templates.webapp.tests import pytest_options import vasl_templates.webapp.tests.utils as test_utils from vasl_templates.webapp.tests.utils import init_webapp, get_nationalities, find_child, wait_for # --------------------------------------------------------------------- -@pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) def test_vo_reports( webapp, webdriver ): #pylint: disable=too-many-locals """Check the vehicle/ordnance reports.""" diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py index 13ad1b0..ec112ce 100644 --- a/vasl_templates/webapp/tests/utils.py +++ b/vasl_templates/webapp/tests/utils.py @@ -10,12 +10,13 @@ import uuid from collections import defaultdict import lxml.html -import pytest from selenium.webdriver.support.ui import Select from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, WebDriverException +import vasl_templates.webapp.tests + # standard templates _STD_TEMPLATES = { "scenario": [ @@ -533,7 +534,7 @@ def _get_clipboard() : in the UI (e.g. clicking a button) and the result appearing in the clipboard, so tests should use wait_for_clipboard() instead. """ - if pytest.config.option.use_clipboard: #pylint: disable=no-member + if vasl_templates.webapp.tests.pytest_options.use_clipboard: global _pyqt_app from PyQt5.QtWidgets import QApplication if _pyqt_app is None: @@ -627,7 +628,7 @@ _IE_HTML_TAGS = [ "" ] def adjust_html( val ): """Adjust HTML content for IE.""" - if pytest.config.option.webdriver != "ie": #pylint: disable=no-member + if vasl_templates.webapp.tests.pytest_options.webdriver != "ie": return val # convert HTML tags to uppercase :-/ for tag in _IE_HTML_TAGS: diff --git a/vasl_templates/webapp/utils.py b/vasl_templates/webapp/utils.py index b22f7d6..a99ea00 100644 --- a/vasl_templates/webapp/utils.py +++ b/vasl_templates/webapp/utils.py @@ -193,7 +193,8 @@ def trim_image( img ): if isinstance( img, str ): img = Image.open( img ) # trim the screenshot (nb: we assume a white background) - bgd = Image.new( img.mode, img.size, (255,255,255,255) ) + img = remove_alpha_from_image( img ) + bgd = Image.new( img.mode, img.size, (255,255,255) ) diff = ImageChops.difference( img, bgd ) bbox = diff.getbbox() return img.crop( bbox ) @@ -207,9 +208,7 @@ def get_image_data( img, **kwargs ): def remove_alpha_from_image( img ): """Remove the alpha channel from an image.""" - img2 = Image.new( "RGB", img.size, "WHITE" ) - img2.paste( img, (0,0), img ) - return img2 + return img.convert( "RGB" ) # --------------------------------------------------------------------- @@ -304,4 +303,3 @@ def make_formatted_day_of_month( dom ): class SimpleError( Exception ): """Represents a simple error that doesn't require a stack trace (e.g. bad configuration).""" - pass diff --git a/vasl_templates/webapp/vassal.py b/vasl_templates/webapp/vassal.py index 1a102db..48cb23b 100644 --- a/vasl_templates/webapp/vassal.py +++ b/vasl_templates/webapp/vassal.py @@ -445,7 +445,7 @@ class VassalShim: try: proc = subprocess.Popen( args2, **kwargs ) except FileNotFoundError as ex: - raise SimpleError( "Can't run the VASSAL shim (have you configured Java?): {}".format( ex ) ) + raise SimpleError( "Can't run the VASSAL shim (have you configured Java?): {}".format( ex ) ) from ex try: proc.wait( timeout ) except subprocess.TimeoutExpired: diff --git a/vasl_templates/webapp/webdriver.py b/vasl_templates/webapp/webdriver.py index e92ecc4..6a9a03c 100644 --- a/vasl_templates/webapp/webdriver.py +++ b/vasl_templates/webapp/webdriver.py @@ -2,11 +2,13 @@ import os import threading +import io import tempfile import atexit import logging from selenium import webdriver +from PIL import Image from vasl_templates.webapp import app, globvars from vasl_templates.webapp.utils import TempFile, SimpleError, trim_image @@ -68,19 +70,19 @@ class WebDriver: kwargs = { "executable_path": webdriver_path } if "chromedriver" in webdriver_path: options = webdriver.ChromeOptions() - options.set_headless( headless=True ) + options.headless = True # OMG! The chromedriver looks for Chrome/Chromium in a hard-coded, fixed location (the default # installation directory). We offer a way here to override this. chrome_path = app.config.get( "CHROME_PATH" ) if chrome_path: options.binary_location = chrome_path - kwargs["chrome_options"] = options + kwargs["options"] = options self.driver = webdriver.Chrome( **kwargs ) elif "geckodriver" in webdriver_path: options = webdriver.FirefoxOptions() - options.set_headless( headless=True ) - kwargs["firefox_options"] = options - kwargs["log_path"] = app.config.get( "GECKODRIVER_LOG", + options.headless = True + kwargs["options"] = options + kwargs["service_log_path"] = app.config.get( "GECKODRIVER_LOG", os.path.join( tempfile.gettempdir(), "geckodriver.log" ) ) self.driver = webdriver.Firefox( **kwargs ) @@ -107,12 +109,12 @@ class WebDriver: def get_screenshot( self, html, window_size, large_window_size=None ): """Get a preview screenshot of the specified HTML.""" - def do_get_screenshot( fname ): #pylint: disable=missing-docstring - self.driver.save_screenshot( fname ) - return trim_image( fname ) + def do_get_screenshot(): #pylint: disable=missing-docstring + data = self.driver.get_screenshot_as_png() + img = Image.open( io.BytesIO( data ) ) + return trim_image( img ) - with TempFile( extn=".html", mode="w", encoding="utf-8" ) as html_tempfile, \ - TempFile( extn=".png" ) as screenshot_tempfile: + with TempFile( extn=".html", mode="w", encoding="utf-8" ) as html_tempfile: # NOTE: We could do some funky Javascript stuff to load the browser directly from the string, # but using a temp file is straight-forward and pretty much guaranteed to work :-/ @@ -121,16 +123,15 @@ class WebDriver: self.driver.get( "file://{}".format( html_tempfile.name ) ) # take a screenshot of the HTML - screenshot_tempfile.close( delete=False ) self.driver.set_window_size( window_size[0], window_size[1] ) - img = do_get_screenshot( screenshot_tempfile.name ) + img = do_get_screenshot() retry_ratio = float( app.config.get( "WEBDRIVER_SCREENSHOT_RETRY_RATIO", 0.8 ) ) if img.width > window_size[0]*retry_ratio or img.height > window_size[1]*retry_ratio: # FUDGE! The webdriver sometimes has trouble when the image is close to the edge of the canvas # (it gets cropped), so we retry even if we appear to be completely within the canvas. if large_window_size: self.driver.set_window_size( large_window_size[0], large_window_size[1] ) - img = do_get_screenshot( screenshot_tempfile.name ) + img = do_get_screenshot() return img def get_snippet_screenshot( self, snippet_id, snippet ):