diff --git a/.pylintrc b/.pylintrc index 95add7c..9553c03 100644 --- a/.pylintrc +++ b/.pylintrc @@ -148,7 +148,8 @@ disable=print-statement, import-outside-toplevel, isinstance-second-argument-not-valid-type, consider-using-f-string, - consider-using-max-builtin + consider-using-max-builtin, + use-implicit-booleaness-not-comparison # 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 diff --git a/Dockerfile b/Dockerfile index 1024a6c..ab81472 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # NOTE: Use the run-container.sh script to build and launch this container. # NOTE: Multi-stage builds require Docker >= 17.05. -FROM centos:8 AS base +FROM rockylinux:8 AS base # update packages and install requirements RUN dnf -y upgrade-minimal && \ @@ -16,14 +16,14 @@ RUN url="https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf65 # install Firefox RUN dnf install -y wget bzip2 xorg-x11-server-Xvfb gtk3 dbus-glib && \ - wget -qO- "https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=en-US" \ + wget -qO- "https://ftp.mozilla.org/pub/firefox/releases/94.0.2/linux-x86_64/en-US/firefox-94.0.2.tar.bz2" \ | tar -C /usr/local/ -jx && \ ln -s /usr/local/firefox/firefox /usr/bin/firefox && \ echo "exclude=firefox" >>/etc/dnf/dnf.conf # install geckodriver -RUN url=$( curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -Poh 'https.*linux64\.tar\.gz(?!\.)' ) && \ - curl -sL "$url" | tar -C /usr/bin/ -xz +RUN curl -sL "https://github.com/mozilla/geckodriver/releases/download/v0.30.0/geckodriver-v0.30.0-linux64.tar.gz" \ + | tar -C /usr/bin/ -xz # clean up RUN dnf clean all @@ -73,6 +73,13 @@ COPY docker/config/ ./vasl_templates/webapp/config/ RUN useradd --create-home app USER app +# FUDGE! We need this to stop spurious warning messages: +# Fork support is only compatible with the epoll1 and poll polling strategies +# Setting the verbosity to ERROR should suppress these, but doesn't :-/ +# https://github.com/grpc/grpc/issues/17253 +# https://github.com/grpc/grpc/blob/master/doc/environment_variables.md +ENV GRPC_VERBOSITY=NONE + # run the application EXPOSE 5010 COPY docker/run.sh ./ diff --git a/conftest.py b/conftest.py index 08c4941..3575f8d 100644 --- a/conftest.py +++ b/conftest.py @@ -1,11 +1,9 @@ """ pytest support functions. """ -import os import threading import json import re import logging -import tempfile import urllib.request from urllib.error import URLError import pytest @@ -147,7 +145,8 @@ def _make_webapp(): def is_ready(): """Try to connect to the webapp server.""" try: - with urllib.request.urlopen( app.url_for("ping") ) as resp: + url = app.url_for( "ping" ) + with urllib.request.urlopen( url ) as resp: assert resp.read().startswith( b"pong: " ) return True except URLError: @@ -200,24 +199,12 @@ def webdriver( request ): if driver == "firefox": options = wb.FirefoxOptions() options.headless = _pytest_options.headless - driver = wb.Firefox( - options = options, - service_log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" ) - ) + driver = wb.Firefox( options=options ) elif driver == "chrome": options = wb.ChromeOptions() 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_options.headless: - raise RuntimeError( "IE WebDriver cannot be run headless." ) - options.IntroduceInstabilityByIgnoringProtectedModeSettings = True - options.EnsureCleanSession = True - driver = wb.Ie( ie_options=options ) else: raise RuntimeError( "Unknown webdriver: {}".format( driver ) ) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2edcd3a..a88efd3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ pytest==6.2.5 -grpcio-tools==1.41.0 +grpcio-tools==1.44.0 tabulate==0.8.9 -lxml==4.6.3 -pylint==2.11.1 +lxml==4.8.0 +pylint==2.12.2 pytest-pylint==0.18.0 -pyinstaller==4.5.1 +pyinstaller==4.9 diff --git a/requirements.txt b/requirements.txt index 5a5b474..8b35cf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ # python 3.8.7 -flask==2.0.1 -pyyaml==5.4.1 -pillow==8.3.2 -selenium==3.141.0 +flask==2.0.3 +pyyaml==6.0 +pillow==9.0.1 +selenium==4.1.0 waitress==2.0.0 -click==8.0.1 +click==8.0.4 diff --git a/vasl_templates/webapp/scenarios.py b/vasl_templates/webapp/scenarios.py index 1e601a2..2d34c61 100644 --- a/vasl_templates/webapp/scenarios.py +++ b/vasl_templates/webapp/scenarios.py @@ -563,8 +563,10 @@ def on_successful_asa_upload( scenario_id ): # download the specified scenario url = app.config["ASA_GET_SCENARIO_URL"].replace( "{ID}", scenario_id ) try: - with urllib.request.urlopen( url ) as fp: - new_scenario = json.loads( fp.read().decode( "utf-8" ) ) + with urllib.request.urlopen( url ) as resp: + new_scenario = json.loads( + resp.read().decode( "utf-8" ) + ) except Exception as ex: #pylint: disable=broad-except msg = str( getattr(ex,"reason",None) or ex ) return jsonify( { "status": "error", "message": msg } ) diff --git a/vasl_templates/webapp/snippets.py b/vasl_templates/webapp/snippets.py index 858f383..36a70dd 100644 --- a/vasl_templates/webapp/snippets.py +++ b/vasl_templates/webapp/snippets.py @@ -204,8 +204,8 @@ def get_flag( nat ): fname = globvars.template_pack.get( "nationalities", {} ).get( nat, {} ).get( "flag" ) if fname: if fname.startswith( ("http://","https://") ): - with urllib.request.urlopen( fname ) as fp: - return _get_small_image( fp, key, height ) + with urllib.request.urlopen( fname ) as resp: + return _get_small_image( resp, key, height ) else: with open( fname, "rb" ) as fp: return _get_small_image( fp, key, height ) diff --git a/vasl_templates/webapp/tests/test_template_packs.py b/vasl_templates/webapp/tests/test_template_packs.py index 205fa1d..13c4dae 100644 --- a/vasl_templates/webapp/tests/test_template_packs.py +++ b/vasl_templates/webapp/tests/test_template_packs.py @@ -6,6 +6,8 @@ import base64 import re import random +from selenium.webdriver.common.by import By + from vasl_templates.webapp.utils import TempFile from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.utils import \ @@ -199,7 +201,7 @@ def test_missing_templates( webapp, webdriver ): disabled = webdriver.execute_script( "return $(arguments[0]).button('option','disabled')", btn ) assert expected == disabled # check that snippet control groups have been enabled/disabled correctly - parent = btn.find_element_by_xpath( ".." ) + parent = btn.find_element( By.XPATH, ".." ) parent_classes = get_css_classes( parent ) if is_snippet_control: assert "snippet-control" in parent_classes diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py index b7ffedf..e4fb219 100644 --- a/vasl_templates/webapp/tests/utils.py +++ b/vasl_templates/webapp/tests/utils.py @@ -13,6 +13,7 @@ import lxml.html from selenium.webdriver.support.ui import Select from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, WebDriverException import vasl_templates.webapp.tests @@ -157,7 +158,7 @@ def select_tab_for_elem( elem ): def get_tab_for_elem( elem ): """Identify the tab that contains the specified element.""" while elem.tag_name not in ("html","body"): - elem = elem.find_element_by_xpath( ".." ) + elem = elem.find_element( By.XPATH, ".." ) if elem.tag_name == "div": div_id = elem.get_attribute( "id" ) if div_id.startswith( "tabs-" ): @@ -431,14 +432,14 @@ def find_child( sel, parent=None ): """Find a single child element.""" try: # NOTE: I tried caching these results, but it didn't help the tests run any faster :-( - return (parent if parent else _webdriver).find_element_by_css_selector( sel ) + return (parent if parent else _webdriver).find_element( By.CSS_SELECTOR, sel ) except NoSuchElementException: return None def find_children( sel, parent=None ): """Find child elements.""" try: - return (parent if parent else _webdriver).find_elements_by_css_selector( sel ) + return (parent if parent else _webdriver).find_elements( By.CSS_SELECTOR, sel ) except NoSuchElementException: return None diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py index d63568f..b668fc9 100644 --- a/vasl_templates/webapp/vo_notes.py +++ b/vasl_templates/webapp/vo_notes.py @@ -438,8 +438,8 @@ def load_asl_rulebook2_vo_note_targets( msg_store ): with open( base_url, "r", encoding="utf-8" ) as fp: _asl_rulebook2_targets = json.load( fp ) else: - with urllib.request.urlopen( base_url + "/vo-note-targets" ) as fp: - _asl_rulebook2_targets = json.load( fp ) + with urllib.request.urlopen( base_url + "/vo-note-targets" ) as resp: + _asl_rulebook2_targets = json.load( resp ) except Exception as ex: #pylint: disable=broad-except msg = str( getattr(ex,"reason",None) or ex ) msg_store.warning( "Couldn't get the ASL Rulebook2 Chapter H targets: {}".format( msg ) ) diff --git a/vasl_templates/webapp/webdriver.py b/vasl_templates/webapp/webdriver.py index c703fe1..39ab7fe 100644 --- a/vasl_templates/webapp/webdriver.py +++ b/vasl_templates/webapp/webdriver.py @@ -67,7 +67,9 @@ class WebDriver: # create the webdriver _logger.debug( "- Launching webdriver process: %s", webdriver_path ) - kwargs = { "executable_path": webdriver_path } + log_fname = app.config.get( "WEBDRIVER_LOG", + os.path.join( tempfile.gettempdir(), "webdriver.log" ) + ) if "chromedriver" in webdriver_path: options = webdriver.ChromeOptions() options.headless = True @@ -78,17 +80,21 @@ class WebDriver: chrome_path = app.config.get( "CHROME_PATH" ) if chrome_path: options.binary_location = chrome_path - kwargs["options"] = options - self.driver = webdriver.Chrome( **kwargs ) + service = webdriver.chrome.service.Service( + webdriver_path, log_path=log_fname + ) + self.driver = webdriver.Chrome( + options=options, service=service + ) elif "geckodriver" in webdriver_path: options = webdriver.FirefoxOptions() options.headless = True - kwargs["options"] = options - kwargs["service_log_path"] = app.config.get( "GECKODRIVER_LOG", - os.path.join( tempfile.gettempdir(), "geckodriver.log" ) + service = webdriver.firefox.service.Service( + webdriver_path, log_path=log_fname + ) + self.driver = webdriver.Firefox( + options=options, proxy=None, service=service ) - kwargs["proxy"] = None - self.driver = webdriver.Firefox( **kwargs ) else: raise SimpleError( "Can't identify webdriver: {}".format( webdriver_path ) ) _logger.debug( "- Started OK." )