Updated to Python 3.8.7.

master
Pacman Ghost 3 years ago
parent 8f7077a884
commit 951b57c6be
  1. 6
      .pylintrc
  2. 4
      Dockerfile
  3. 36
      conftest.py
  4. 6
      freeze.py
  5. 14
      requirements-dev.txt
  6. 10
      requirements.txt
  7. 5
      setup.py
  8. 2
      tools/build_file.py
  9. 7
      vasl_templates/main.py
  10. 5
      vasl_templates/main_window.py
  11. 4
      vasl_templates/tools/dump_log_file_analysis.py
  12. 2
      vasl_templates/webapp/scenarios.py
  13. 5
      vasl_templates/webapp/static/help/index.html
  14. 3
      vasl_templates/webapp/tests/__init__.py
  15. 13
      vasl_templates/webapp/tests/test_capabilities.py
  16. 3
      vasl_templates/webapp/tests/test_counters.py
  17. 4
      vasl_templates/webapp/tests/test_dirty_scenario_checks.py
  18. 3
      vasl_templates/webapp/tests/test_jshint.py
  19. 3
      vasl_templates/webapp/tests/test_scenario_search.py
  20. 3
      vasl_templates/webapp/tests/test_snippets.py
  21. 5
      vasl_templates/webapp/tests/test_vassal.py
  22. 5
      vasl_templates/webapp/tests/test_vehicles_ordnance.py
  23. 3
      vasl_templates/webapp/tests/test_vo_notes.py
  24. 3
      vasl_templates/webapp/tests/test_vo_reports.py
  25. 7
      vasl_templates/webapp/tests/utils.py
  26. 8
      vasl_templates/webapp/utils.py
  27. 2
      vasl_templates/webapp/vassal.py
  28. 27
      vasl_templates/webapp/webdriver.py

@ -144,7 +144,9 @@ disable=print-statement,
duplicate-code, # can't get it to shut up about @pytest.mark.skipif's :-/ duplicate-code, # can't get it to shut up about @pytest.mark.skipif's :-/
no-else-return, no-else-return,
len-as-condition, 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 # 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 # either give multiple identifier separated by comma (,) or put this option
@ -511,7 +513,7 @@ max-public-methods=20
max-returns=10 max-returns=10
# Maximum number of statements in function / method body # Maximum number of statements in function / method body
max-statements=80 max-statements=100
# Minimum number of public methods for a class (see R0903). # Minimum number of public methods for a class (see R0903).
min-public-methods=2 min-public-methods=2

@ -9,7 +9,7 @@ FROM centos:8 AS base
RUN dnf -y upgrade-minimal RUN dnf -y upgrade-minimal
# install Python # 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 FROM base AS build
# set up a virtualenv # 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" ENV PATH="/opt/venv/bin:$PATH"
# install the application requirements # install the application requirements

@ -18,6 +18,8 @@ from vasl_templates.webapp.tests.control_tests import ControlTests
FLASK_WEBAPP_PORT = 5011 FLASK_WEBAPP_PORT = 5011
_pytest_options = None
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def pytest_addoption( parser ): def pytest_addoption( parser ):
@ -59,6 +61,15 @@ def pytest_addoption( parser ):
help="Use the clipboard to get snippets." 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 _webapp = None
@ -85,7 +96,7 @@ def _make_webapp():
"""Create the global webapp fixture.""" """Create the global webapp fixture."""
# initialize # 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://" ): if webapp_url and not webapp_url.startswith( "http://" ):
webapp_url = "http://" + webapp_url webapp_url = "http://" + webapp_url
app.base_url = webapp_url if webapp_url else "http://localhost:{}".format( FLASK_WEBAPP_PORT ) 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 # stop the browser from checking for a dirty scenario when leaving the page
kwargs["disable_close_window_check"] = 1 kwargs["disable_close_window_check"] = 1
# check if the tests are being run headless # 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 :-/ # 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 # 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, # 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. # to avoid problems if something else uses the clipboard while the tests are running.
kwargs["store_clipboard"] = 1 kwargs["store_clipboard"] = 1
@ -152,7 +163,7 @@ def _make_webapp():
) )
except urllib.error.HTTPError as ex: except urllib.error.HTTPError as ex:
if ex.code == 404: 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 raise
port_no = resp.get( "port" ) port_no = resp.get( "port" )
if not port_no: if not port_no:
@ -188,20 +199,21 @@ def webdriver( request ):
from selenium import webdriver as wb from selenium import webdriver as wb
if driver == "firefox": if driver == "firefox":
options = wb.FirefoxOptions() options = wb.FirefoxOptions()
options.set_headless( headless = pytest.config.option.headless ) #pylint: disable=no-member options.headless = _pytest_options.headless
driver = wb.Firefox( driver = wb.Firefox(
firefox_options = options, options = options,
log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" ) service_log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" )
) )
elif driver == "chrome": elif driver == "chrome":
options = wb.ChromeOptions() options = wb.ChromeOptions()
options.set_headless( headless = pytest.config.option.headless ) #pylint: disable=no-member options.headless = _pytest_options.headless
driver = wb.Chrome( chrome_options=options ) options.add_argument( "--disable-gpu" )
driver = wb.Chrome( options=options )
elif driver == "ie": elif driver == "ie":
# NOTE: IE11 requires a registry key to be set: # NOTE: IE11 requires a registry key to be set:
# https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver#required-configuration # https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver#required-configuration
options = wb.IeOptions() options = wb.IeOptions()
if pytest.config.option.headless: #pylint: disable=no-member if _pytest_options.headless:
raise RuntimeError( "IE WebDriver cannot be run headless." ) raise RuntimeError( "IE WebDriver cannot be run headless." )
options.IntroduceInstabilityByIgnoringProtectedModeSettings = True options.IntroduceInstabilityByIgnoringProtectedModeSettings = True
options.EnsureCleanSession = True options.EnsureCleanSession = True
@ -210,7 +222,7 @@ def webdriver( request ):
raise RuntimeError( "Unknown webdriver: {}".format( driver ) ) raise RuntimeError( "Unknown webdriver: {}".format( driver ) )
# set the browser size # 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]) ) driver.set_window_size( int(words[0]), int(words[1]) )
# return the webdriver to the caller # return the webdriver to the caller

@ -32,7 +32,8 @@ def get_git_info():
# get the latest commit ID # get the latest commit ID
proc = subprocess.run( proc = subprocess.run(
[ "git", "log" ], [ "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] buf = proc.stdout.split( "\n" )[0]
mo = re.search( r"^commit ([a-z0-9]+)$", buf ) mo = re.search( r"^commit ([a-z0-9]+)$", buf )
@ -41,7 +42,8 @@ def get_git_info():
# get the current git branch # get the current git branch
proc = subprocess.run( proc = subprocess.run(
[ "git", "branch" ], [ "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("* ") ] lines = [ s for s in proc.stdout.split("\n") if s.startswith("* ") ]
if len(lines) != 1: if len(lines) != 1:

@ -1,7 +1,7 @@
pytest==3.6.0 pytest==6.2.1
grpcio-tools==1.33.2 grpcio-tools==1.34.1
tabulate==0.8.2 tabulate==0.8.7
lxml==4.2.4 lxml==4.6.2
pylint==1.9.2 pylint==2.6.0
pytest-pylint==0.9.0 pytest-pylint==0.18.0
pyinstaller==3.6 pyinstaller==4.2

@ -1,7 +1,7 @@
# python 3.6.8 # python 3.8.7
flask==1.0.2 flask==1.1.2
pyyaml==5.3.1 pyyaml==5.3.1
pillow==7.0.0 pillow==8.1.0
selenium==3.12.0 selenium==3.141.0
click==6.7 click==7.1.2

@ -37,8 +37,9 @@ setup(
"gui": [ "gui": [
# NOTE: PyQt5 requirements: https://doc.qt.io/qt-5/linux.html # NOTE: PyQt5 requirements: https://doc.qt.io/qt-5/linux.html
# Linux: mesa-libGL-devel ; @"C Development Tools and Libraries" # Linux: mesa-libGL-devel ; @"C Development Tools and Libraries"
# nb: WebEngine seems to be broken in 5.10.1 :-/ # NOTE: You may need to disable VMware 3D acceleration, if QWebEngineView is crashing.
"PyQT5==5.10.0", "PyQT5==5.15.2",
"PyQtWebEngine==5.15.2",
], ],
"dev": parse_requirements( "requirements-dev.txt" ), "dev": parse_requirements( "requirements-dev.txt" ),
}, },

@ -46,7 +46,7 @@ class BuildFile:
val = click.style( child.tag, fg="green" ) val = click.style( child.tag, fg="green" )
if line_nos: if line_nos:
val += ":{}".format( click.style( str(child.sourceline), fg="cyan" ) ) 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() click.echo()
# dump any attributes # dump any attributes
if attribs: if attribs:

@ -179,10 +179,9 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin
raise SimpleError( "Unexpected server check response: {}".format( resp ) ) raise SimpleError( "Unexpected server check response: {}".format( resp ) )
if resp[6:] == INSTANCE_ID: if resp[6:] == INSTANCE_ID:
break break
else: from vasl_templates.webapp.config.constants import APP_NAME
from vasl_templates.webapp.config.constants import APP_NAME QMessageBox.warning( None, APP_NAME, "The program is already running." )
QMessageBox.warning( None, APP_NAME, "The program is already running." ) return -1
return -1
except URLError: except URLError:
# no response - the webapp server is probably still starting up # no response - the webapp server is probably still starting up
time.sleep( 0.25 ) time.sleep( 0.25 )

@ -97,7 +97,7 @@ class MainWindow( QWidget ):
# initialize the layout # initialize the layout
layout = QVBoxLayout( self ) 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 # 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). # 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), # By disabling it, the program will at least start (in particular, the webapp server),
@ -165,6 +165,9 @@ class MainWindow( QWidget ):
if self._view: if self._view:
app_settings.setValue( "MainWindow/geometry", self.saveGeometry() ) app_settings.setValue( "MainWindow/geometry", self.saveGeometry() )
self.close() 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 # check if the scenario is dirty
def callback( is_dirty ): def callback( is_dirty ):

@ -179,7 +179,7 @@ def dump_time_plot( players, log_file, roll_type, window_size ):
"""Dump the buffered ROLL events.""" """Dump the buffered ROLL events."""
print( tabulate.tabulate( rolls, tablefmt="plain" ) ) 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.""" """Process a TURN TRACK event."""
nonlocal rolls nonlocal rolls
if 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( "--- {} Turn {} {} ---".format( evt["side"], evt["turnNo"], evt["phase"] ) )
print() print()
def onRoll( evt ) : #pylint: disable=unused-variable def onRoll( evt ) : #pylint: disable=unused-variable,possibly-unused-variable
"""Process a ROLL event""" """Process a ROLL event"""
# check if we should process this ROLL event # check if we should process this ROLL event
if roll_type: if roll_type:

@ -385,7 +385,7 @@ def get_scenario_card( scenario_id ): #pylint: disable=too-many-branches
min_turns = scenario.get( "min_turns", "0" ) min_turns = scenario.get( "min_turns", "0" )
max_turns = scenario.get( "max_turns", "0" ) max_turns = scenario.get( "max_turns", "0" )
if min_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" ) args[ "TURN_COUNT" ] = friendly_fractions( min_turns, "turn", "turns" )
elif max_turns != "0": elif max_turns != "0":
args[ "TURN_COUNT" ] = "{}-{} turns".format( friendly_fractions(min_turns), friendly_fractions(max_turns) ) args[ "TURN_COUNT" ] = "{}-{} turns".format( friendly_fractions(min_turns), friendly_fractions(max_turns) )

@ -52,7 +52,7 @@
<p> If you're on a Mac or Linux, you can run the program directly from the source code. Get a copy from <a href="https://github.com/pacman-ghost/vasl-templates">Github</a> in the usual way, by <tt>git clone</tt>'ing it, or downloading a ZIP and unpacking it somewhere. <p> If you're on a Mac or Linux, you can run the program directly from the source code. Get a copy from <a href="https://github.com/pacman-ghost/vasl-templates">Github</a> in the usual way, by <tt>git clone</tt>'ing it, or downloading a ZIP and unpacking it somewhere.
<p> 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 <em>should</em> work. <p> 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 <em>should</em> work.
<p> While not essential, it is <em>strongly</em> recommended that you set up a <a href="https://virtualenv.pypa.io/en/stable/">virtual environment</a> first. Then, install the requirements: <p> While not essential, it is <em>strongly</em> recommended that you set up a <a href="https://virtualenv.pypa.io/en/stable/">virtual environment</a> first. Then, install the requirements:
<div class="code"> <div class="code">
@ -63,7 +63,8 @@ pip install .[gui]
<p> If you're on Windows, the Qt runtime will have been installed as part of PyQt5 (when you did the <tt>pip install</tt> above), but if you're in a virtual environment and you're getting <em>"DLL load failed"</em> errors, this is due to a problem with the way Python sets up the virtualenv. In the virtualenv's <tt>scripts/</tt> sub-directory, there should be <em>two</em> Python DLL's, so if you're missing <tt>python3.dll</tt>, copy it over from the Python installation the virtualenv was created from, and you should be good to go. <p> If you're on Windows, the Qt runtime will have been installed as part of PyQt5 (when you did the <tt>pip install</tt> above), but if you're in a virtual environment and you're getting <em>"DLL load failed"</em> errors, this is due to a problem with the way Python sets up the virtualenv. In the virtualenv's <tt>scripts/</tt> sub-directory, there should be <em>two</em> Python DLL's, so if you're missing <tt>python3.dll</tt>, copy it over from the Python installation the virtualenv was created from, and you should be good to go.
<p> 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 <a href="https://www.qt.io/download">installer</a>. <p> If you're on Linux, you <em>may</em> need to install Qt 5.15.2. On Fedora 33, running the "gui" install above should install everything you need.
<p> Then, just run the <tt>vasl-templates</tt> command. <p> Then, just run the <tt>vasl-templates</tt> command.
<h4> Running just the web server </h4> <h4> Running just the web server </h4>

@ -0,0 +1,3 @@
"""Module definitions."""
pytest_options = None

@ -10,6 +10,7 @@ from vasl_templates.webapp.tests.utils import \
init_webapp, select_menu_option, select_tab, click_dialog_button, \ init_webapp, select_menu_option, select_tab, click_dialog_button, \
find_child, find_children, wait_for_clipboard, \ find_child, find_children, wait_for_clipboard, \
set_scenario_date 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_vo_reports import get_vo_report
from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo
from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario 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 def test_month_capabilities( webapp, webdriver ): #pylint: disable=too-many-statements
"""Test date-based capabilities that change in the middle of a year.""" """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 ): def test_kfw( webapp, webdriver ):
"""Test date-based capabilities for K:FW vehicles/ordnance.""" """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 ): def test_theater_capabilities( webapp, webdriver ):
"""Test theater-specific capabilities.""" """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 ): def test_theater_capabilities_bfp( webapp, webdriver ):
"""Test theater-specific capabilities (BFP extension).""" """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 ): def test_american_ordnance_note_c( webapp, webdriver ):
"""Test handling of American Ordnance Note C.""" """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 ): def test_nationality_capabilities( webapp, webdriver ):
"""Test nationality-specific capabilities.""" """Test nationality-specific capabilities."""

@ -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.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.vo import _kfw_listings #pylint: disable=protected-access
from vasl_templates.webapp.utils import compare_version_strings 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.utils import init_webapp, select_tab, find_child, find_children
from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario 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 def test_counter_images( webapp, webdriver ): #pylint: disable=too-many-locals
"""Test that counter images are served correctly.""" """Test that counter images are served correctly."""

@ -2,9 +2,9 @@
import re import re
import pytest
from selenium.webdriver.support.ui import Select 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_scenario_persistence import ALL_SCENARIO_PARAMS
from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo
from vasl_templates.webapp.tests.utils import \ 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 tab_id,params in ALL_SCENARIO_PARAMS.items():
for param in params: for param in params:
do_test( tab_id, param ) 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 :-/ break # nb: it's a bit excessive to check *every* parameter :-/

@ -35,7 +35,8 @@ def test_jshint():
# run JSHint for the next file # run JSHint for the next file
proc = subprocess.run( proc = subprocess.run(
[ jshint, os.path.join(dname,fname) ], [ 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: if proc.stdout or proc.stderr:
print( "=== JSHint failed: {} ===".format( fname ) ) print( "=== JSHint failed: {} ===".format( fname ) )

@ -10,6 +10,7 @@ from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import StaleElementReferenceException 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_scenario_persistence import save_scenario, load_scenario
from vasl_templates.webapp.tests.test_vassal import run_vassal_tests from vasl_templates.webapp.tests.test_vassal import run_vassal_tests
from vasl_templates.webapp.tests.utils import init_webapp, select_tab, new_scenario, \ 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.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." reason = "Can't test against a remote webapp server."
) )
def test_roar_matching2( webapp, webdriver ): def test_roar_matching2( webapp, webdriver ):

@ -6,6 +6,7 @@ import pytest
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from vasl_templates.webapp.tests import pytest_options
from vasl_templates.webapp.tests.utils import \ from vasl_templates.webapp.tests.utils import \
init_webapp, select_tab, find_snippet_buttons, set_template_params, wait_for, wait_for_clipboard, \ 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, \ 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, # 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) :-( # and I just couldn't get things to work (not even reloading the page each time helped) :-(
@pytest.mark.skipif( @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." reason="Selenium problems (?) cause these tests to fail under Firefox."
) )
def test_snippet_images( webapp, webdriver ): def test_snippet_images( webapp, webdriver ):

@ -7,9 +7,8 @@ import base64
import random import random
import typing.re #pylint: disable=import-error 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.utils import TempFile, change_extn, compare_version_strings
from vasl_templates.webapp.tests import pytest_options
from vasl_templates.webapp.tests.utils import \ from vasl_templates.webapp.tests.utils import \
init_webapp, select_menu_option, get_stored_msg, set_stored_msg, set_stored_msg_marker, wait_for, \ init_webapp, select_menu_option, get_stored_msg, set_stored_msg, set_stored_msg_marker, wait_for, \
new_scenario, set_player, find_child 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, # 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. # and since they all should give the same results, it doesn't matter which one.
if all_combos is None: 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: if not all_combos:
for _ in range(0,100): for _ in range(0,100):
vasl_version = random.choice( vasl_versions ) vasl_version = random.choice( vasl_versions )

@ -8,6 +8,7 @@ import pytest
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys 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.test_scenario_persistence import load_scenario, save_scenario
from vasl_templates.webapp.tests.utils import \ from vasl_templates.webapp.tests.utils import \
init_webapp, get_nationalities, select_tab, set_template_params, find_child, find_children, \ 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 def test_common_vo( webapp, webdriver ): #pylint: disable=too-many-locals
"""Test loading of common vehicles/ordnance and landing craft.""" """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 if nat in ["thai","indonesian","anzac","burmese","filipino"]: # nb: these are in the BFP extension
assert not elem.is_enabled() assert not elem.is_enabled()
continue continue
elif nat == "kfw-cpva" and vo_type == "vehicles": if nat == "kfw-cpva" and vo_type == "vehicles":
assert not elem.is_enabled() assert not elem.is_enabled()
continue continue
elem.click() elem.click()

@ -10,6 +10,7 @@ import lxml.html
import lxml.etree import lxml.etree
import tabulate import tabulate
from vasl_templates.webapp.tests import pytest_options
from vasl_templates.webapp.tests.utils import \ from vasl_templates.webapp.tests.utils import \
init_webapp, get_nationalities, select_tab, set_player, select_menu_option, click_dialog_button, \ init_webapp, get_nationalities, select_tab, set_player, select_menu_option, click_dialog_button, \
find_child, find_children, wait_for, wait_for_clipboard 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 def test_vo_notes_reports( webapp, webdriver ): #pylint: disable=too-many-locals
"""Check the vehicle/ordnance notes reports.""" """Check the vehicle/ordnance notes reports."""

@ -10,12 +10,13 @@ import lxml.html
import lxml.etree import lxml.etree
import tabulate import tabulate
from vasl_templates.webapp.tests import pytest_options
import vasl_templates.webapp.tests.utils as test_utils import vasl_templates.webapp.tests.utils as test_utils
from vasl_templates.webapp.tests.utils import init_webapp, get_nationalities, find_child, wait_for 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 def test_vo_reports( webapp, webdriver ): #pylint: disable=too-many-locals
"""Check the vehicle/ordnance reports.""" """Check the vehicle/ordnance reports."""

@ -10,12 +10,13 @@ import uuid
from collections import defaultdict from collections import defaultdict
import lxml.html import lxml.html
import pytest
from selenium.webdriver.support.ui import Select from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, WebDriverException from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, WebDriverException
import vasl_templates.webapp.tests
# standard templates # standard templates
_STD_TEMPLATES = { _STD_TEMPLATES = {
"scenario": [ "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 in the UI (e.g. clicking a button) and the result appearing in the clipboard, so tests
should use wait_for_clipboard() instead. 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 global _pyqt_app
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
if _pyqt_app is None: if _pyqt_app is None:
@ -627,7 +628,7 @@ _IE_HTML_TAGS = [ "<i>" ]
def adjust_html( val ): def adjust_html( val ):
"""Adjust HTML content for IE.""" """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 return val
# convert HTML tags to uppercase :-/ # convert HTML tags to uppercase :-/
for tag in _IE_HTML_TAGS: for tag in _IE_HTML_TAGS:

@ -193,7 +193,8 @@ def trim_image( img ):
if isinstance( img, str ): if isinstance( img, str ):
img = Image.open( img ) img = Image.open( img )
# trim the screenshot (nb: we assume a white background) # 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 ) diff = ImageChops.difference( img, bgd )
bbox = diff.getbbox() bbox = diff.getbbox()
return img.crop( bbox ) return img.crop( bbox )
@ -207,9 +208,7 @@ def get_image_data( img, **kwargs ):
def remove_alpha_from_image( img ): def remove_alpha_from_image( img ):
"""Remove the alpha channel from an image.""" """Remove the alpha channel from an image."""
img2 = Image.new( "RGB", img.size, "WHITE" ) return img.convert( "RGB" )
img2.paste( img, (0,0), img )
return img2
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@ -304,4 +303,3 @@ def make_formatted_day_of_month( dom ):
class SimpleError( Exception ): class SimpleError( Exception ):
"""Represents a simple error that doesn't require a stack trace (e.g. bad configuration).""" """Represents a simple error that doesn't require a stack trace (e.g. bad configuration)."""
pass

@ -445,7 +445,7 @@ class VassalShim:
try: try:
proc = subprocess.Popen( args2, **kwargs ) proc = subprocess.Popen( args2, **kwargs )
except FileNotFoundError as ex: 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: try:
proc.wait( timeout ) proc.wait( timeout )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:

@ -2,11 +2,13 @@
import os import os
import threading import threading
import io
import tempfile import tempfile
import atexit import atexit
import logging import logging
from selenium import webdriver from selenium import webdriver
from PIL import Image
from vasl_templates.webapp import app, globvars from vasl_templates.webapp import app, globvars
from vasl_templates.webapp.utils import TempFile, SimpleError, trim_image from vasl_templates.webapp.utils import TempFile, SimpleError, trim_image
@ -68,19 +70,19 @@ class WebDriver:
kwargs = { "executable_path": webdriver_path } kwargs = { "executable_path": webdriver_path }
if "chromedriver" in webdriver_path: if "chromedriver" in webdriver_path:
options = webdriver.ChromeOptions() 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 # 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. # installation directory). We offer a way here to override this.
chrome_path = app.config.get( "CHROME_PATH" ) chrome_path = app.config.get( "CHROME_PATH" )
if chrome_path: if chrome_path:
options.binary_location = chrome_path options.binary_location = chrome_path
kwargs["chrome_options"] = options kwargs["options"] = options
self.driver = webdriver.Chrome( **kwargs ) self.driver = webdriver.Chrome( **kwargs )
elif "geckodriver" in webdriver_path: elif "geckodriver" in webdriver_path:
options = webdriver.FirefoxOptions() options = webdriver.FirefoxOptions()
options.set_headless( headless=True ) options.headless = True
kwargs["firefox_options"] = options kwargs["options"] = options
kwargs["log_path"] = app.config.get( "GECKODRIVER_LOG", kwargs["service_log_path"] = app.config.get( "GECKODRIVER_LOG",
os.path.join( tempfile.gettempdir(), "geckodriver.log" ) os.path.join( tempfile.gettempdir(), "geckodriver.log" )
) )
self.driver = webdriver.Firefox( **kwargs ) self.driver = webdriver.Firefox( **kwargs )
@ -107,12 +109,12 @@ class WebDriver:
def get_screenshot( self, html, window_size, large_window_size=None ): def get_screenshot( self, html, window_size, large_window_size=None ):
"""Get a preview screenshot of the specified HTML.""" """Get a preview screenshot of the specified HTML."""
def do_get_screenshot( fname ): #pylint: disable=missing-docstring def do_get_screenshot(): #pylint: disable=missing-docstring
self.driver.save_screenshot( fname ) data = self.driver.get_screenshot_as_png()
return trim_image( fname ) img = Image.open( io.BytesIO( data ) )
return trim_image( img )
with TempFile( extn=".html", mode="w", encoding="utf-8" ) as html_tempfile, \ with TempFile( extn=".html", mode="w", encoding="utf-8" ) as html_tempfile:
TempFile( extn=".png" ) as screenshot_tempfile:
# NOTE: We could do some funky Javascript stuff to load the browser directly from the string, # 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 :-/ # 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 ) ) self.driver.get( "file://{}".format( html_tempfile.name ) )
# take a screenshot of the HTML # take a screenshot of the HTML
screenshot_tempfile.close( delete=False )
self.driver.set_window_size( window_size[0], window_size[1] ) 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 ) ) 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: 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 # 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. # (it gets cropped), so we retry even if we appear to be completely within the canvas.
if large_window_size: if large_window_size:
self.driver.set_window_size( large_window_size[0], large_window_size[1] ) 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 return img
def get_snippet_screenshot( self, snippet_id, snippet ): def get_snippet_screenshot( self, snippet_id, snippet ):

Loading…
Cancel
Save