|
|
|
""" Helper utilities. """
|
|
|
|
|
|
|
|
import os
|
|
|
|
import urllib.request
|
|
|
|
import json
|
|
|
|
import time
|
|
|
|
import re
|
|
|
|
|
|
|
|
from PyQt5.QtWidgets import QApplication
|
|
|
|
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
|
|
|
|
|
|
|
|
# standard templates
|
|
|
|
_STD_TEMPLATES = {
|
|
|
|
"scenario": [ "scenario", "players", "victory_conditions", "scenario_notes", "ssr" ],
|
|
|
|
"ob1": [ "ob_setup_1", "ob_note_1", "vehicles_1", "ordnance_1" ],
|
|
|
|
"ob2": [ "ob_setup_2", "ob_note_2", "vehicles_2", "ordnance_2" ],
|
|
|
|
}
|
|
|
|
|
|
|
|
# nationality-specific templates
|
|
|
|
_NAT_TEMPLATES = {
|
|
|
|
"german": [ "pf", "psk", "atmm" ],
|
|
|
|
"russian": [ "mol", "mol-p" ],
|
|
|
|
"american": [ "baz" ],
|
|
|
|
"british": [ "piat" ],
|
|
|
|
}
|
|
|
|
|
|
|
|
_webdriver = None
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
def for_each_template( func ): #pylint: disable=too-many-branches
|
|
|
|
"""Test each template."""
|
|
|
|
|
|
|
|
# generate a list of all the templates we need to test
|
|
|
|
templates_to_test = set()
|
|
|
|
dname = os.path.join( os.path.split(__file__)[0], "../data/default-template-pack" )
|
|
|
|
for fname in os.listdir(dname):
|
|
|
|
fname,extn = os.path.splitext( fname )
|
|
|
|
if extn != ".j2":
|
|
|
|
continue
|
|
|
|
templates_to_test.add( fname )
|
|
|
|
|
|
|
|
# test the standard templates
|
|
|
|
for tab_id,template_ids in _STD_TEMPLATES.items():
|
|
|
|
for template_id in template_ids:
|
|
|
|
select_tab( tab_id )
|
|
|
|
orig_template_id = template_id
|
|
|
|
if template_id == "scenario_notes":
|
|
|
|
template_id = "scenario_note"
|
|
|
|
elif template_id.startswith( "ob_setup_" ):
|
|
|
|
template_id = "ob_setup"
|
|
|
|
elif template_id.startswith( "ob_note_" ):
|
|
|
|
template_id = "ob_note"
|
|
|
|
elif template_id.startswith( "vehicles_" ):
|
|
|
|
template_id = "vehicles"
|
|
|
|
elif template_id.startswith( "ordnance_" ):
|
|
|
|
template_id = "ordnance"
|
|
|
|
func( template_id, orig_template_id )
|
|
|
|
if orig_template_id not in ("ob_setup_2","ob_note_2","vehicles_2","ordnance_2"):
|
|
|
|
templates_to_test.remove( template_id )
|
|
|
|
|
|
|
|
# test the nationality-specific templates
|
|
|
|
# NOTE: The buttons are the same on the OB1 and OB2 tabs, so we only test for player 1.
|
|
|
|
player1_sel = Select( find_child( "select[name='PLAYER_1']" ) )
|
|
|
|
for nat,template_ids in _NAT_TEMPLATES.items():
|
|
|
|
select_tab( "scenario" )
|
|
|
|
player1_sel.select_by_value( nat )
|
|
|
|
select_tab( "ob1" )
|
|
|
|
for template_id in template_ids:
|
|
|
|
func( template_id, template_id )
|
|
|
|
templates_to_test.remove( template_id )
|
|
|
|
|
|
|
|
# make sure we tested everything
|
|
|
|
assert not templates_to_test
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def select_tab( tab_id ):
|
|
|
|
"""Select a tab in the main page."""
|
|
|
|
elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-{}']".format( tab_id ) )
|
|
|
|
elem.click()
|
|
|
|
|
|
|
|
def select_menu_option( menu_id ):
|
|
|
|
"""Select a menu option."""
|
|
|
|
elem = find_child( "#menu" )
|
|
|
|
elem.click()
|
|
|
|
elem = find_child( "a.PopMenu-Link[data-name='{}']".format( menu_id ) )
|
|
|
|
elem.click()
|
|
|
|
wait_for( 5, lambda: find_child("#menu .PopMenu-Container") is None ) # nb: wait for the menu to go away
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def set_template_params( params ): #pylint: disable=too-many-branches
|
|
|
|
"""Set template parameters."""
|
|
|
|
|
|
|
|
def add_sortable_entries( sortable, entries ):
|
|
|
|
"""Add simple notes to a sortable."""
|
|
|
|
for entry in entries:
|
|
|
|
add_simple_note( sortable, entry.get("caption",""), entry.get("width","") )
|
|
|
|
|
|
|
|
for key,val in params.items():
|
|
|
|
|
|
|
|
# check for scenario notes (these require special handling)
|
|
|
|
if key == "SCENARIO_NOTES":
|
|
|
|
# add them in (nb: we don't consider any existing scenario notes)
|
|
|
|
add_sortable_entries( find_child("#scenario_notes-sortable"), val )
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check for SSR's (these require special handling)
|
|
|
|
if key == "SSR":
|
|
|
|
# add them in (nb: we don't consider any existing SSR's)
|
|
|
|
sortable = find_child( "#ssr-sortable" )
|
|
|
|
for ssr in val:
|
|
|
|
add_simple_note( sortable, ssr, None )
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check for OB setups/notes (these require special handling)
|
|
|
|
if key in ("OB_SETUPS_1","OB_SETUPS_2","OB_NOTES_1","OB_NOTES_2"):
|
|
|
|
# add them in (nb: we don't consider any existing OB setup/note's)
|
|
|
|
mo = re.search( r"^(.*)_(\d)$", key )
|
|
|
|
sortable = find_child( "#{}-sortable_{}".format( mo.group(1).lower(), mo.group(2) ) )
|
|
|
|
add_sortable_entries( sortable, val )
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check for vehicles/ordnance (these require special handling)
|
|
|
|
if key in ("VEHICLES_1","ORDNANCE_1","VEHICLES_2","ORDNANCE_2"):
|
|
|
|
# add them in (nb: we don't consider any existing vehicles/ordnance)
|
|
|
|
from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo #pylint: disable=cyclic-import
|
|
|
|
vo_type = key[:key.index("_")].lower()
|
|
|
|
for vo_name in val:
|
|
|
|
add_vo( vo_type, int(key[-1]), vo_name )
|
|
|
|
continue
|
|
|
|
|
|
|
|
# locate the next parameter
|
|
|
|
elem = next( c for c in ( \
|
|
|
|
find_child( "{}[name='{}']".format(elem_type,key) ) \
|
|
|
|
for elem_type in ["input","textarea","select"]
|
|
|
|
) if c )
|
|
|
|
|
|
|
|
# set the parameter value
|
|
|
|
if elem.tag_name == "select":
|
|
|
|
Select(elem).select_by_value( val )
|
|
|
|
else:
|
|
|
|
elem.clear()
|
|
|
|
if val:
|
|
|
|
elem.send_keys( val )
|
|
|
|
if key == "SCENARIO_DATE":
|
|
|
|
elem.send_keys( Keys.ESCAPE ) # nb: force the calendar popup to close :-/
|
|
|
|
wait_for( 5, lambda: find_child("#ui-datepicker-div").value_of_css_property("display") == "none" )
|
|
|
|
time.sleep( 0.25 )
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def get_nationalities( webapp ):
|
|
|
|
"""Get the nationalities table."""
|
|
|
|
url = webapp.url_for( "get_template_pack" )
|
|
|
|
template_pack = json.load( urllib.request.urlopen( url ) )
|
|
|
|
return template_pack["nationalities"]
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
def add_simple_note( sortable, caption, width ):
|
|
|
|
"""Add a new simple note to a sortable."""
|
|
|
|
edit_simple_note( sortable, None, caption, width )
|
|
|
|
|
|
|
|
def edit_simple_note( sortable, entry_no, caption, width ):
|
|
|
|
"""Edit a simple note in a sortable."""
|
|
|
|
|
|
|
|
# figure out if we're creating a new entry, or editing an existing one
|
|
|
|
if entry_no is None:
|
|
|
|
# create a new entry
|
|
|
|
add_button = find_sortable_helper( sortable, "add" )
|
|
|
|
add_button.click()
|
|
|
|
else:
|
|
|
|
# edit an existing entry
|
|
|
|
elems = find_children( "li", sortable )
|
|
|
|
ActionChains(_webdriver).double_click( elems[entry_no] ).perform()
|
|
|
|
|
|
|
|
# edit the note
|
|
|
|
if caption is not None:
|
|
|
|
elem = find_child( "#edit-simple_note textarea" )
|
|
|
|
elem.clear()
|
|
|
|
elem.send_keys( caption )
|
|
|
|
if width is not None:
|
|
|
|
elem = find_child( "#edit-simple_note input[type='text']" )
|
|
|
|
elem.clear()
|
|
|
|
elem.send_keys( width )
|
|
|
|
click_dialog_button( "OK" )
|
|
|
|
if caption == "":
|
|
|
|
# an empty caption will delete the entry - confirm the deletion
|
|
|
|
click_dialog_button( "OK" )
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def get_sortable_entry_text( sortable ):
|
|
|
|
"""Get the text for each entry in a sortable."""
|
|
|
|
return [ c.text for c in find_children("li", sortable) ]
|
|
|
|
|
|
|
|
def get_sortable_entry_count( sortable ):
|
|
|
|
"""Return the number of entries in a sortable."""
|
|
|
|
return len( find_children( "li", sortable ) )
|
|
|
|
|
|
|
|
def generate_sortable_entry_snippet( sortable, entry_no ):
|
|
|
|
"""Generate the snippet for a sortable entry."""
|
|
|
|
elems = find_children( "li img.snippet", sortable )
|
|
|
|
elems[entry_no].click()
|
|
|
|
return get_clipboard()
|
|
|
|
|
|
|
|
def drag_sortable_entry_to_trash( sortable, entry_no ):
|
|
|
|
"""Draw a sortable entry to the trash."""
|
|
|
|
trash = find_sortable_helper( sortable, "trash" )
|
|
|
|
elems = find_children( "li", sortable )
|
|
|
|
ActionChains(_webdriver).drag_and_drop( elems[entry_no], trash ).perform()
|
|
|
|
|
|
|
|
def find_sortable_helper( sortable, tag ):
|
|
|
|
"""Find a sortable's helper element."""
|
|
|
|
sortable_id = sortable.get_attribute( "id" )
|
|
|
|
mo = re.search( r"^(.+)-sortable(_\d)?$", sortable_id )
|
|
|
|
helper_id = "#{}-{}".format( mo.group(1), tag )
|
|
|
|
if mo.group(2):
|
|
|
|
helper_id += mo.group(2)
|
|
|
|
return find_child( helper_id )
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
def get_stored_msg( msg_id ):
|
|
|
|
"""Get a message stored for us by the front-end."""
|
|
|
|
elem = find_child( "#"+msg_id )
|
|
|
|
if not elem:
|
|
|
|
return None
|
|
|
|
if elem.tag_name == "div":
|
|
|
|
return elem.text
|
|
|
|
assert elem.tag_name == "textarea"
|
|
|
|
return elem.get_attribute( "value" )
|
|
|
|
|
|
|
|
def set_stored_msg( msg_id, val ):
|
|
|
|
"""Set a message for the front-end."""
|
|
|
|
elem = find_child( "#"+msg_id )
|
|
|
|
assert elem.tag_name == "textarea"
|
|
|
|
_webdriver.execute_script( "arguments[0].value = arguments[1]", elem, val )
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
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 )
|
|
|
|
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 )
|
|
|
|
except NoSuchElementException:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def dismiss_notifications():
|
|
|
|
"""Dismiss all notifications."""
|
|
|
|
while True:
|
|
|
|
elem = find_child( ".growl-close" )
|
|
|
|
if not elem:
|
|
|
|
break
|
|
|
|
try:
|
|
|
|
elem.click()
|
|
|
|
time.sleep( 0.25 )
|
|
|
|
except StaleElementReferenceException:
|
|
|
|
pass # nb: the notification had already auto-closed
|
|
|
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
def click_dialog_button( caption ):
|
|
|
|
"""Click a dialog button."""
|
|
|
|
btn = next(
|
|
|
|
elem for elem in find_children(".ui-dialog button")
|
|
|
|
if elem.text == caption
|
|
|
|
)
|
|
|
|
btn.click()
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
def get_clipboard() :
|
|
|
|
"""Get the contents of the clipboard."""
|
|
|
|
app = QApplication( [] ) #pylint: disable=unused-variable
|
|
|
|
clipboard = QApplication.clipboard()
|
|
|
|
return clipboard.text()
|
|
|
|
|
|
|
|
def wait_for( timeout, func ):
|
|
|
|
"""Wait for a condition to become true."""
|
|
|
|
start_time = time.time()
|
|
|
|
while True:
|
|
|
|
if func():
|
|
|
|
break
|
|
|
|
assert time.time() - start_time < timeout
|
|
|
|
time.sleep( 0.1 )
|