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 ):