You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
9.9 KiB
285 lines
9.9 KiB
""" Helper utilities. """
|
|
|
|
import sys
|
|
import os
|
|
import urllib.request
|
|
import json
|
|
import re
|
|
import uuid
|
|
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
|
|
|
from asl_rulebook2.utils import strip_html
|
|
from asl_rulebook2.webapp import tests as webapp_tests
|
|
|
|
_webapp = None
|
|
_webdriver = None
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def init_webapp( webapp, webdriver, **options ):
|
|
"""Initialize the webapp."""
|
|
|
|
# initialize
|
|
expected_warnings = options.pop( "expected_warnings", [] )
|
|
expected_errors = options.pop( "expected_errors", [] )
|
|
|
|
# initialize
|
|
global _webapp, _webdriver
|
|
_webapp = webapp
|
|
_webdriver = webdriver
|
|
options = {
|
|
key.replace("_","-"): val
|
|
for key, val in options.items()
|
|
}
|
|
|
|
# load the webapp
|
|
webapp.control_tests.set_app_config_val( "BLOCKING_STARTUP_TASKS", True )
|
|
webdriver.get( make_webapp_main_url( options ) )
|
|
_wait_for_webapp()
|
|
|
|
# make sure there were no errors or warnings
|
|
url = webapp.url_for( "get_startup_msgs" )
|
|
with urllib.request.urlopen( url ) as resp:
|
|
startup_msgs = json.load( resp )
|
|
errors = startup_msgs.pop( "error", [] )
|
|
errors = [ e[0] for e in errors ]
|
|
assert set( errors ) == set( expected_errors )
|
|
warnings = startup_msgs.pop( "warning", [] )
|
|
warnings = [ w[0] for w in warnings ]
|
|
assert set( warnings ) == set( expected_warnings )
|
|
assert not startup_msgs
|
|
|
|
# reset the user settings
|
|
webdriver.delete_all_cookies()
|
|
|
|
def make_webapp_main_url( options ):
|
|
"""Generate the webapp URL."""
|
|
if get_pytest_option("webdriver") == "chrome" and get_pytest_option("headless"):
|
|
# FUDGE! Headless Chrome doesn't want to show the PDF in the browser,
|
|
# it downloads the file and saves it in the current directory :wtf:
|
|
options["no-content"] = 1
|
|
options["store-msgs"] = 1 # nb: so that we can retrive notification messages
|
|
options["no-animations"] = 1
|
|
options["reload"] = 1 # nb: force the webapp to reload
|
|
return _webapp.url_for( "main", **options )
|
|
|
|
def refresh_webapp( webdriver ):
|
|
"""Refresh the webapp."""
|
|
webdriver.refresh()
|
|
_wait_for_webapp()
|
|
|
|
def _wait_for_webapp():
|
|
"""Wait for the webapp to finish initialization."""
|
|
timeout = 5
|
|
wait_for( timeout,
|
|
lambda: find_child("#_mainapp-loaded_") \
|
|
or ( webapp_tests.pytest_options.enable_prepare and find_child("#_prepareapp-loaded_") )
|
|
)
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def select_tabbed_page( tabbed_pages_id, tab_id ):
|
|
"""Select a tabbed page."""
|
|
tabbed_pages = find_child( "#tabbed-pages-" + tabbed_pages_id )
|
|
assert tabbed_pages
|
|
btn = find_child( ".tab-strip .tab[data-tabid='{}']".format( tab_id ), tabbed_pages )
|
|
btn.click()
|
|
def find_tabbed_page():
|
|
elem = find_child( ".tabbed-page[data-tabid='{}']".format( tab_id ), tabbed_pages )
|
|
return elem and elem.is_displayed()
|
|
wait_for( 2, find_tabbed_page )
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def get_nav_panels():
|
|
"""Get the available nav panels."""
|
|
return _get_tab_ids( "#nav .tab-strip" )
|
|
|
|
def get_content_docs():
|
|
"""Get the available content docs."""
|
|
return _get_tab_ids( "#content .tab-strip" )
|
|
|
|
def _get_tab_ids( sel ):
|
|
"""Get the tabs in a tab-strip."""
|
|
tabs = find_children( "{} .tab".format( sel ) )
|
|
return [ tab.get_attribute( "data-tabid" ) for tab in tabs ]
|
|
|
|
def get_curr_target():
|
|
"""Get the currently-shown target."""
|
|
# check the active tab
|
|
elem = find_child( "#content .tab-strip .tab.active" )
|
|
if not elem:
|
|
return ( None, None )
|
|
tab_id = elem.get_attribute( "data-tabid" )
|
|
# check the current ruleid
|
|
elem = find_child( "#content .tabbed-page[data-tabid='{}'] .content-doc".format( tab_id ) )
|
|
ruleid = elem.get_attribute( "data-ruleid" )
|
|
if ruleid:
|
|
return ( tab_id, ruleid )
|
|
page_no = elem.get_attribute( "data-pageno" )
|
|
if page_no:
|
|
return ( tab_id, int(page_no) )
|
|
return tab_id
|
|
|
|
def check_sr_filters( expected ):
|
|
"""Check the search result filter checkboxes."""
|
|
sr_filters = find_child( "#search-box .sr-filters" )
|
|
if expected:
|
|
elems = [
|
|
c.get_attribute("name") for c in find_children( "input[type='checkbox']", sr_filters )
|
|
if c.is_displayed()
|
|
]
|
|
assert all( e.startswith("show-") and e.endswith("-sr") for e in elems )
|
|
elems = [ e[5:-3] for e in elems ]
|
|
assert elems == expected
|
|
else:
|
|
assert not sr_filters.is_displayed()
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
#pylint: disable=multiple-statements,missing-function-docstring
|
|
def get_last_info(): return get_stored_msg( "info" )
|
|
def get_last_warning_msg(): return get_stored_msg( "warning" )
|
|
def get_last_error_msg(): return get_stored_msg( "error" )
|
|
def get_last_footnote_msg(): return get_stored_msg( "footnote" )
|
|
#pylint: enable=multiple-statements,missing-function-docstring
|
|
|
|
def get_stored_msg( msg_type ):
|
|
"""Get a message stored for us by the front-end."""
|
|
elem = find_child( "#_last-{}-msg_".format(msg_type), _webdriver )
|
|
assert elem.tag_name == "textarea"
|
|
return elem.get_attribute( "value" )
|
|
|
|
def set_stored_msg( msg_type, val ):
|
|
"""Set a message for the front-end."""
|
|
elem = find_child( "#_last-{}-msg_".format(msg_type), _webdriver )
|
|
assert elem.tag_name == "textarea"
|
|
_webdriver.execute_script( "arguments[0].value = arguments[1]", elem, val )
|
|
|
|
def set_stored_msg_marker( msg_type ):
|
|
"""Store marker text in the message buffer (so we can tell if the front-end changes it)."""
|
|
marker = "marker:{}:{}".format( msg_type, uuid.uuid4() )
|
|
set_stored_msg( msg_type, marker )
|
|
return marker
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def find_child( sel, parent=None ):
|
|
"""Find a single child element."""
|
|
try:
|
|
if parent is None:
|
|
parent = _webdriver
|
|
return parent.find_element( By.CSS_SELECTOR, sel )
|
|
except NoSuchElementException:
|
|
return None
|
|
|
|
def find_children( sel, parent=None ):
|
|
"""Find child elements."""
|
|
try:
|
|
if parent is None:
|
|
parent = _webdriver
|
|
return parent.find_elements( By.CSS_SELECTOR, sel )
|
|
except NoSuchElementException:
|
|
return None
|
|
|
|
def get_classes( elem ):
|
|
"""Get the element's classes."""
|
|
classes = elem.get_attribute( "class" )
|
|
return classes.split()
|
|
|
|
def has_class( elem, class_name ):
|
|
"""Check if an element has a specified CSS class."""
|
|
return class_name in get_classes( elem )
|
|
|
|
def unload_elem( save_loc, key, elem, adjust_hilites=False ):
|
|
"""Unload a single element."""
|
|
if not elem:
|
|
return False
|
|
if elem.tag_name in ("div", "span"):
|
|
val = unload_sr_text( elem ) if adjust_hilites else elem.text
|
|
elif elem.tag_name == "img":
|
|
val = get_image_filename( elem )
|
|
else:
|
|
assert False, "Unknown element type: " + elem.tag_name
|
|
return False
|
|
if not val:
|
|
return False
|
|
save_loc[ key ] = val
|
|
return True
|
|
|
|
def unload_sr_text( elem ):
|
|
"""Unload a text value that is part of a search result."""
|
|
val = elem.get_attribute( "innerHTML" )
|
|
# change how highlighted content is represented
|
|
matches = list( re.finditer( r'<span class="hilite">(.*?)</span>', val ) )
|
|
for mo in reversed(matches):
|
|
val = val[:mo.start()] + "((" + mo.group(1) + "))" + val[mo.end():]
|
|
# remove HTML tags
|
|
val = strip_html( val ).strip()
|
|
return val
|
|
|
|
def get_image_filename( elem, full=False ):
|
|
"""Get the filename of an <img> element."""
|
|
if elem is None:
|
|
return None
|
|
assert elem.tag_name == "img"
|
|
src = elem.get_attribute( "src" )
|
|
if full:
|
|
src = re.sub( r"^http://[^/]+", "", src )
|
|
else:
|
|
src = os.path.basename( src )
|
|
return re.sub( r"/+", "/", src )
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def wait_for_elem( timeout, sel, parent=None ):
|
|
"""Wait for an element to appear."""
|
|
elem = None
|
|
def check_elem():
|
|
nonlocal elem
|
|
elem = find_child( sel, parent )
|
|
return elem is not None and elem.is_displayed()
|
|
wait_for( timeout, check_elem )
|
|
return elem
|
|
|
|
def wait_for( timeout, func ):
|
|
"""Wait for a condition to become true."""
|
|
last_val = None
|
|
def wrapper( driver ): #pylint: disable=unused-argument
|
|
nonlocal last_val
|
|
last_val = func()
|
|
return last_val
|
|
WebDriverWait( _webdriver, timeout, poll_frequency=0.1 ).until( wrapper )
|
|
return last_val
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
#pylint: disable=missing-function-docstring
|
|
def wait_for_info_msg( timeout, expected, contains=True ):
|
|
return _do_wait_for_msg( timeout, "info", expected, contains )
|
|
def wait_for_warning_msg( timeout, expected, contains=True ):
|
|
return _do_wait_for_msg( timeout, "warning", expected, contains )
|
|
def wait_for_error_msg( timeout, expected, contains=True ):
|
|
return _do_wait_for_msg( timeout, "error", expected, contains )
|
|
#pylint: enable=missing-function-docstring
|
|
|
|
def _do_wait_for_msg( timeout, msg_type, expected, contains ):
|
|
"""Wait for a message to be issued."""
|
|
func = getattr( sys.modules[__name__], "get_last_{}_msg".format( msg_type ) )
|
|
try:
|
|
wait_for( timeout,
|
|
lambda: expected in func() if contains else expected == func()
|
|
)
|
|
except TimeoutException:
|
|
print( "ERROR: Didn't get expected {} message: {}".format( msg_type, expected ) )
|
|
print( "- last {} message: {}".format( msg_type, func() ) )
|
|
assert False
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def get_pytest_option( opt ):
|
|
"""Get a pytest configuration option."""
|
|
return getattr( webapp_tests.pytest_options, opt )
|
|
|