"""" Test scenario search. """ import os import base64 import time import pytest 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, \ set_player, set_template_params, set_scenario_date, get_player_nat, get_theater, set_theater, \ get_turn_track_nturns, \ wait_for, wait_for_elem, find_child, find_children, get_css_classes, set_stored_msg, click_dialog_button # --------------------------------------------------------------------- def test_scenario_cards( webapp, webdriver ): """Test showing scenario cards.""" # initialize init_webapp( webapp, webdriver ) # search for the "full" scenario and check the scenario card _do_scenario_search( "full", [1], webdriver ) card = _unload_scenario_card() assert card == { "scenario_name": "Full content scenario", "scenario_id": "FCS-1", "scenario_url": "https://aslscenarioarchive.com/scenario.php?id=1", "scenario_location": "Some place", "scenario_date": "31st December, 1945", "theater": "PTO", "turn_count": "4-5", "playing_time": "1\u00bc hours", "icons": [ "aslsk.png", "deluxe.png", "night.png", "oba.png" ], "designer": "Joe Author", "publication": "ASL Journal", "publication_url": "https://aslscenarioarchive.com/viewPub.php?id=PUB-1", "publication_date": "3rd February, 2001", "publisher": "Avalon Hill", "publisher_url": "https://aslscenarioarchive.com/viewPublisher.php?id=42", "prev_publication": "Prior version", "revised_publication": "Revised version", "overview": "This is a really good scenario.", "map_url": "MAP:[1.23,4.56]", "defender_name": "Dutch", "defender_desc": "1st Dutch Army", "attacker_name": "Romanian", "attacker_desc": "1st Romanian Army", "balances": { "asa": [ { "name": "Dutch", "wins": 3, "percentage": 30 }, { "name": "Romanian", "wins": 7, "percentage": 70 } ] }, "oba": [ [ "Dutch", "1B", "1R" ], [ "Romanian", "-", "-" ] ], "boards": "1, 2, RB (4)", # n: the (4) is the number of map previews "map_previews": [ "chuck:1600000000|screenshot3.png", "bob:1500000000|screenshot2.png", "alice:1400000000|screenshot1.png", "asl-scenario-archive.png" ], "overlays": "1, 2, OG1", "extra_rules": "Some extra rules", "errata": [ [ "Errata #1", "over there" ], [ "Errata #2", "right here" ] ] } # search for the "empty" scenario and check the scenario card _do_scenario_search( "Untitled", ["no-content"], webdriver ) card = _unload_scenario_card() assert card == { "scenario_name": "Untitled scenario (#no-content)", "scenario_url": "https://aslscenarioarchive.com/scenario.php?id=no-content", } # --------------------------------------------------------------------- def test_import_scenario( webapp, webdriver ): """Test importing a scenario.""" # initialize init_webapp( webapp, webdriver ) # import the "full" scenario dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() wait_for( 2, lambda: _check_scenario( SCENARIO_NAME="Full content scenario", SCENARIO_ID="FCS-1", SCENARIO_LOCATION="Some place", SCENARIO_TURNS="5", PLAYER_1="dutch", PLAYER_1_DESCRIPTION="1st Dutch Army", PLAYER_2="romanian", PLAYER_2_DESCRIPTION="1st Romanian Army", THEATER="PTO" ) ) # import the "empty" scenario _unlink_scenario() dlg = _do_scenario_search( "Untitled", ["no-content"], webdriver ) _import_scenario_and_confirm( dlg ) # NOTE: Since there are no players defined in the scenario, what's on-screen will be left unchanged. wait_for( 2, lambda: _check_scenario( SCENARIO_NAME="Untitled scenario (#no-content)", SCENARIO_ID="", SCENARIO_LOCATION="", SCENARIO_TURNS="", PLAYER_1="dutch", PLAYER_1_DESCRIPTION="", PLAYER_2="romanian", PLAYER_2_DESCRIPTION="", THEATER="ETO" ) ) def _check_scenario( **kwargs ): """Check the scenario import.""" for key in ["SCENARIO_NAME","SCENARIO_ID","SCENARIO_LOCATION","PLAYER_1_DESCRIPTION","PLAYER_2_DESCRIPTION"]: elem = find_child( "input[name='{}']".format( key ) ) if elem.get_attribute( "value" ) != kwargs[ key ]: return False if get_player_nat( 1 ) != kwargs["PLAYER_1"] or get_player_nat( 2 ) != kwargs["PLAYER_2"]: return False if get_theater() != kwargs[ "THEATER" ]: return False if get_turn_track_nturns() != kwargs["SCENARIO_TURNS"]: return False return True # --------------------------------------------------------------------- def test_import_warnings( webapp, webdriver ): #pylint: disable=too-many-statements """Test warnings when importing a scenario.""" # initialize init_webapp( webapp, webdriver, scenario_persistence=1 ) # import a scenario on top of an empty scenario dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() wait_for( 2, lambda: not dlg.is_displayed() ) def check_warnings( expected ): #pylint: disable=missing-docstring warnings = find_children( ".warnings input[type='checkbox']", dlg ) return [ w.get_attribute( "name" ) for w in warnings ] == expected def do_test( param_name, expected_warning, expected_val, curr_val="CURR-VAL" ): #pylint: disable=missing-docstring # start with a new scenario new_scenario() # set the scenario parameter set_template_params( { param_name: curr_val } ) # import a scenario _do_scenario_search( "full", [1], webdriver ) _click_import_button( find_child( "#scenario-search" ) ) # check if any warnings were expected elem = find_child( "[name='{}']".format( param_name ) ) if expected_warning: # yup - make sure they are being shown wait_for( 2, lambda: check_warnings( [expected_warning] ) ) # cancel the import find_child( "button.cancel-import", dlg ).click() wait_for( 2, lambda: not find_child( ".warnings", dlg ).is_displayed() ) # do the import again, and accept it _import_scenario_and_confirm( dlg ) assert elem.get_attribute( "value" ) == expected_val else: # nope - check that the import was done wait_for( 2, lambda: not dlg.is_displayed() ) # assert not dlg.is_displayed() assert elem.get_attribute( "value" ) == expected_val # do the tests do_test( "SCENARIO_NAME", "scenario_name", "Full content scenario" ) do_test( "SCENARIO_ID", "scenario_display_id", "FCS-1" ) do_test( "SCENARIO_LOCATION", "scenario_location", "Some place" ) do_test( "SCENARIO_DATE", "scenario_date_iso", "12/31/1945", curr_val="01/02/1940" ) do_test( "SCENARIO_THEATER", None, "PTO", curr_val="Burma" ) do_test( "PLAYER_1", None, "dutch", curr_val="german" ) do_test( "PLAYER_1_DESCRIPTION", "defender_desc", "1st Dutch Army" ) do_test( "PLAYER_2", None, "romanian", curr_val="german" ) do_test( "PLAYER_2_DESCRIPTION", "attacker_desc", "1st Romanian Army" ) # test importing a scenario on top of existing OB owned by the same nationality new_scenario() load_scenario( { "PLAYER_1": "dutch", } ) dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() check_warnings( [] ) wait_for( 2, lambda: not dlg.is_displayed() ) # test importing a scenario on top of existing OB owned by the same nationality new_scenario() load_scenario( { "PLAYER_1": "dutch", "OB_SETUPS_1": [ { "caption": "Dutch setup note" } ] } ) dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() wait_for( 2, lambda: not dlg.is_displayed() ) # test importing a scenario on top of existing OB owned by the different nationality new_scenario() load_scenario( { "PLAYER_1": "german", "OB_SETUPS_1": [ { "caption": "German setup note" } ] } ) dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() warnings = wait_for( 2, lambda: find_children( ".warnings input[type='checkbox']", dlg ) ) assert [ w.get_attribute( "name" ) for w in warnings ] == [ "defender_name" ] assert not warnings[0].is_selected() wait_for( 2, lambda: warnings[0].is_displayed ) time.sleep( 0.1 ) # nb: wait for the slide to finish :-/ warnings[0].click() find_child( "button.confirm-import", dlg ).click() assert not dlg.is_displayed() assert get_player_nat( 1 ) == "dutch" # --------------------------------------------------------------------- def test_oba_info( webapp, webdriver ): """Test showing OBA info in the scenario card.""" # initialize init_webapp( webapp, webdriver ) def check_oba_info( card, expected ): """Check the OBA info.""" assert card["oba"] == expected assert "oba.png" in card["icons"] # search for the "OBA test" scenario dlg = _do_scenario_search( "OBA test", ["5a"], webdriver ) expected = [ [ "Finnish", "6B", "3R", "Plentiful Ammo included" ], [ "German", "1B", "2R", "a comment", "another comment" ] ] check_oba_info( _unload_scenario_card(), expected ) # import the scenario and check the OBA info find_child( "button.import", dlg ).click() wait_for( 2, lambda: not dlg.is_displayed() ) check_oba_info( _get_scenario_info(), expected ) # change the scenario date and check the OBA info set_scenario_date( "01/02/1943" ) check_oba_info( _get_scenario_info(), [ [ "Finnish", "8B", "3R", "Plentiful Ammo included" ], [ "German", "1B", "2R", "a comment", "another comment" ], "Based on a scenario date of 1/43." ] ) # change the scenario date and check the OBA info set_scenario_date( "" ) check_oba_info( _get_scenario_info(), expected ) # check a scenario where only the defender has OBA _do_scenario_search( "Defender OBA", ["5b"], webdriver ) check_oba_info( _unload_scenario_card(), [ [ "Burmese", "-", "-" ], None ] ) # check a scenario where the attacker has OBA, the defender is an unknwon nationality _do_scenario_search( "Attacker OBA", ["5c"], webdriver ) check_oba_info( _unload_scenario_card(), [ [ "The Other Guy", "?", "?" ], [ "Russian", "3B", "4R" ] ] ) # --------------------------------------------------------------------- def test_unknown_theaters( webapp, webdriver ): """Test importing scenarios with unknown theaters.""" # initialize init_webapp( webapp, webdriver ) # search for the "MTO" scenario (this has a theater mapping) set_theater( "Korea" ) dlg = _do_scenario_search( "MTO", ["3a"], webdriver ) _click_import_button( dlg ) wait_for( 2, lambda: not dlg.is_displayed() ) assert get_theater() == "ETO" # search for the "Africa" scenario (this has no theater mapping) new_scenario() set_theater( "Korea" ) dlg = _do_scenario_search( "Africa", ["3b"], webdriver ) _click_import_button( dlg ) _check_warnings( [], ["Unknown theater: Africa"] ) find_child( "button.confirm-import", dlg ).click() assert get_theater() == "other" # --------------------------------------------------------------------- def test_unknown_nats( webapp, webdriver ): """Test importing scenarios with unknown player nationalities.""" # initialize init_webapp( webapp, webdriver ) # test importing a scenario with 2 completely unknown player nationalities set_player( 1, "french" ) set_player( 2, "italian" ) dlg = _do_scenario_search( "Unknown players", ["4a"], webdriver ) _click_import_button( dlg ) _check_warnings( [], ["Unknown player: Eastasia","Unknown player: Oceania"] ) expected_bgraphs = { "asa": [ { "name": "Eastasia", "wins": 2, "percentage": 67 }, { "name": "Oceania", "wins": 1, "percentage": 33 } ] } assert _unload_balance_graphs( dlg ) == expected_bgraphs find_child( "button.confirm-import", dlg ).click() wait_for( 2, lambda: not find_child( "#scenario-search" ).is_displayed() ) assert get_player_nat( 1 ) == "french" assert get_player_nat( 2 ) == "italian" # test matching nationalities (partial name matches) new_scenario() dlg = _do_scenario_search( "partial nationality matches", ["4b"], webdriver ) find_child( "button.import", dlg ).click() wait_for( 2, lambda: not find_child( "#scenario-search" ).is_displayed() ) assert get_player_nat( 1 ) == "russian" assert get_player_nat( 2 ) == "japanese" # test nationality mapping new_scenario() dlg = _do_scenario_search( "nationality mapping", ["4c"], webdriver ) find_child( "button.import", dlg ).click() wait_for( 2, lambda: not find_child( "#scenario-search" ).is_displayed() ) assert get_player_nat( 1 ) == "british" assert get_player_nat( 2 ) == "british~canadian" # --------------------------------------------------------------------- def test_roar_matching( webapp, webdriver ): """Test matching scenarios with ROAR scenarios.""" # initialize init_webapp( webapp, webdriver ) # search for the "full" scenario _do_scenario_search( "full", [1], webdriver ) card = _unload_scenario_card() assert card["balances"] == { "asa": [ { "name": "Dutch", "wins": 3, "percentage": 30 }, { "name": "Romanian", "wins": 7, "percentage": 70 } ] } # search for the "empty" scenario _do_scenario_search( "Untitled", ["no-content"], webdriver ) card = _unload_scenario_card() assert "balances" not in card # search for "Fighting Withdrawal" _do_scenario_search( "Withdrawal", ["2"], webdriver ) card = _unload_scenario_card() assert card["balances"] == { # NOTE: The 2 sides in the ROAR balance graph should have been swapped around # to match what's in the ASL Scenario Archive. "roar": [ { "name": "Russian", "wins": 325, "percentage": 54 }, { "name": "Finnish", "wins": 279, "percentage": 46 } ], "asa": [ { "name": "Russians", "wins": 78, "percentage": 58 }, # nb: the player nationality has a trailing "s" { "name": "Finnish", "wins": 56, "percentage": 42 } ] } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @pytest.mark.skipif( pytest_options.webapp_url is not None, reason = "Can't test against a remote webapp server." ) def test_roar_matching2( webapp, webdriver ): """Test matching scenarios with ROAR scenarios.""" # initialize init_webapp( webapp, webdriver ) from vasl_templates.webapp.scenarios import _asa_scenarios, _match_roar_scenario def do_test( scenario_name, expected ): #pylint: disable=missing-docstring scenarios = [ s for s in _asa_scenarios.index.values() #pylint: disable=no-member if s.get( "title" ) == scenario_name ] assert len(scenarios) == 1 matches = _match_roar_scenario( scenarios[0] ) if not isinstance( expected, list ): expected = [ expected ] assert [ ( m["roar_id"], m["name"] ) for m in matches ] == expected with _asa_scenarios: # check for no match do_test( "Full content scenario", [] ) # check for an exact match do_test( "ROAR Exact Match", ("200","!! ROAR exact-match !!") ) # check for multiple matches, resolved by the scenario ID do_test( "ROAR Exact Match 2", ("211","ROAR Exact Match 2") ) # check for multiple matches # NOTE: These should be sorted in descending order of number of playings. do_test( "ROAR Multiple Matches", [ ("222","ROAR Multiple Matches"), ("220","ROAR Multiple Matches"), ("221","ROAR Multiple Matches") ] ) # --------------------------------------------------------------------- def test_roar_linking( webapp, webdriver ): """Test linking scenarios with ROAR scenarios.""" # initialize init_webapp( webapp, webdriver ) def check( bgraph, connect, disconnect ): """Check the scenario card.""" # check if the balance graph is shown try: card = _unload_scenario_card() except StaleElementReferenceException: # NOTE: We can get here if the scenario card is reloaded while we're reading it. return False if bgraph: balance = card["balances"].get( "roar" ) if not balance: return False if balance[0]["name"] != bgraph[0] or balance[1]["name"] != bgraph[1]: return False else: if "roar" in card["balances"]: return False # check if the "connect to ROAR" button is shown elem = find_child( "#scenario-info-dialog .connect-roar" ) if connect: if not elem.is_displayed(): return False else: if elem and elem.is_displayed(): return False # check if the "disconnect from ROAR" is shown elem = find_child( "#scenario-info-dialog .disconnect-roar" ) if disconnect: if not elem.is_displayed(): return False else: if elem and elem.is_displayed(): return False return True def disconnect_roar(): """Disconnect the scenario from ROAR.""" btn = find_child( "#scenario-info-dialog .disconnect-roar" ) btn.click() wait_for( 2, lambda: not btn.is_displayed() ) wait_for( 2, lambda: check( None, True, False ) ) # import the "Fighting Withdrawal" scenario dlg = _do_scenario_search( "withdrawal", [2], webdriver ) wait_for( 2, lambda: check( ["Russian","Finnish"], False, False ) ) find_child( "#scenario-search button.import" ).click() wait_for( 2, lambda: not dlg.is_displayed() ) # connect to another ROAR scenario find_child( "button.scenario-search" ).click() wait_for( 2, lambda: check( ["Russian","Finnish"], False, True ) ) disconnect_roar() find_child( "#scenario-info-dialog .connect-roar" ).click() dlg = wait_for_elem( 2, ".ui-dialog.select-roar-scenario" ) find_child( ".select2-search__field", dlg ).send_keys( "another" ) find_child( ".select2-search__field", dlg ).send_keys( Keys.RETURN ) wait_for( 2, lambda: check( ["British","French"], False, True ) ) find_child( ".ui-dialog.scenario-info button.ok" ).click() # disconnect from the ROAR scenario find_child( "button.scenario-search" ).click() wait_for( 2, lambda: check( ["British","French"], False, True ) ) disconnect_roar() find_child( ".ui-dialog.scenario-info button.ok" ).click() # connect to a ROAR scenario find_child( "button.scenario-search" ).click() wait_for( 2, lambda: check( None, True, False ) ) find_child( "#scenario-info-dialog .connect-roar" ).click() dlg = wait_for_elem( 2, ".ui-dialog.select-roar-scenario" ) elem = find_child( ".select2-search__field", dlg ) elem.send_keys( "withdrawal" ) elem.send_keys( Keys.RETURN ) wait_for( 2, lambda: check( ["Russian","Finnish"], False, True ) ) find_child( ".ui-dialog.scenario-info button.ok" ).click() # --------------------------------------------------------------------- def test_scenario_linking( webapp, webdriver ): """Test linking scenarios with the ASL Scenario Archive.""" # initialize init_webapp( webapp, webdriver, scenario_persistence=1 ) def get_asa_id(): """Get the ASL Scenario Archive scenario ID.""" return find_child( "input[name='ASA_ID']" ).get_attribute( "value" ) def check( asa_id ): """Check the current state of the scenario.""" # check that the ASL Scenario Archive scenario ID has been set wait_for( 2, lambda: get_asa_id() == asa_id ) # check that the ASL Scenario Archive scenario ID is saved correctly saved_scenario = save_scenario() assert saved_scenario[ "ASA_ID" ] == asa_id # reset the scenario new_scenario() assert get_asa_id() == "" # check that the ASL Scenario Archive scenario ID is loaded correctly load_scenario( saved_scenario ) assert get_asa_id() == asa_id # import the "full" scenario _do_scenario_search( "full", [1], webdriver ) find_child( "#scenario-search button.import" ).click() check( "1" ) # import the "empty" scenario (on top of the current scenario) dlg = _do_scenario_search( "Untitled", ["no-content"], webdriver ) _import_scenario_and_confirm( dlg ) check( "no-content" ) # import the "Fighting Withdrawal" scenario (on top of the current scenario) dlg = _do_scenario_search( "Fighting Withdrawal", [2], webdriver ) _import_scenario_and_confirm( dlg ) check( "2" ) # unlink the scenario _unlink_scenario() check( "" ) # --------------------------------------------------------------------- def test_scenario_upload( webapp, webdriver ): """Test uploading scenarios to the ASL Scenario Archive.""" # initialize init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1 ) def do_upload( prep_upload, expect_ask ): """Upload the scenario to our test endpoint.""" # show the scenario card find_child( "button.scenario-search" ).click() wait_for( 2, _find_scenario_card ) # open the upload dialog find_child( ".ui-dialog.scenario-info button.upload" ).click() dlg = wait_for_elem( 2, ".ui-dialog.scenario-upload" ) if prep_upload: prep_upload( dlg ) # start the upload webapp.control_tests.reset_last_asa_upload() find_child( "button.upload", dlg ).click() if expect_ask: dlg = wait_for_elem( 2, ".ui-dialog.ask" ) find_child( "button.ok", dlg ).click() # wait for the upload to be processed last_asa_upload = wait_for( 5, webapp.control_tests.get_last_asa_upload ) assert last_asa_upload["user"] == user_name assert last_asa_upload["token"] == api_token return last_asa_upload user_name, api_token = "joe", "xyz123" def prep_upload( dlg ): """Prepare the upload.""" assert find_child( ".scenario-name", dlg ).text == "Full content scenario" assert find_child( ".scenario-id", dlg ).text == "(FCS-1)" assert find_child( ".asa-id", dlg ).text == "(#1)" # NOTE: We only set the auth details once, then they should be remembered. find_child( "input.user", dlg ).send_keys( user_name ) find_child( "input.token", dlg ).send_keys( api_token ) # test uploading just a vasl-templates setup dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() asa_upload = do_upload( prep_upload, True ) assert asa_upload["user"] == user_name assert asa_upload["token"] == api_token assert "vasl_setup" not in asa_upload assert "screenshot" not in asa_upload # compare the vasl-templates setup saved_scenario = save_scenario() keys = [ k for k in saved_scenario if k.startswith("_") ] for key in keys: del saved_scenario[ key ] del asa_upload["vt_setup"][ key ] del saved_scenario[ "VICTORY_CONDITIONS" ] del saved_scenario[ "SSR" ] assert asa_upload["vt_setup"] == saved_scenario def prep_upload2( dlg ): """Prepare the upload.""" assert asa_upload["user"] == user_name assert asa_upload["token"] == api_token # send the VSAV data to the front-end fname = os.path.join( os.path.dirname(__file__), "fixtures/update-vsav/full.vsav" ) with open( fname, "rb" ) as fp: vsav_data = fp.read() set_stored_msg( "_vsav-persistence_", base64.b64encode( vsav_data ).decode( "utf-8" ) ) find_child( ".vsav-container", dlg ).click() # wait for the files to be prepared wait_for( 60, lambda: "loader.gif" not in find_child( ".screenshot-container .preview img" ).get_attribute( "src" ) ) # test uploading a VASL save file def do_test(): #pylint: disable=missing-docstring init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1 ) dlg = _do_scenario_search( "full", [1], webdriver ) find_child( "button.import", dlg ).click() last_asa_upload = do_upload( prep_upload2, False ) assert isinstance( last_asa_upload["vt_setup"], dict ) assert last_asa_upload["vasl_setup"][:2] == b"PK" assert last_asa_upload["screenshot"][:2] == b"\xff\xd8" \ and last_asa_upload["screenshot"][-2:] == b"\xff\xd9" # nb: these are the magic numbers for JPEG's run_vassal_tests( webapp, do_test ) # --------------------------------------------------------------------- def test_scenario_downloads( webapp, webdriver ): """Test downloading files from the ASL Scenario Archive.""" # initialize init_webapp( webapp, webdriver ) def unload_downloads(): """Unload the available downloads.""" btn = find_child( ".ui-dialog.scenario-search .import-control button.downloads" ) if not btn.is_enabled(): return None btn.click() dlg = wait_for_elem( 2, "#scenario-downloads-dialog" ) # unload the file groups fgroups = [] for elem in find_children( ".fgroup", dlg ): fgroup = { "screenshot": fixup( find_child( ".screenshot img", elem ).get_attribute( "src" ) ), "user": fixup( find_child( ".user", elem ).text ), "timestamp": fixup( find_child( ".timestamp", elem ).text ), } add_download_url( fgroup, "vt_setup", elem ) add_download_url( fgroup, "vasl_setup", elem ) fgroups.append( fgroup ) return fgroups def add_download_url( fgroup, key, parent ): #pylint: disable=missing-docstring btn = find_child( "."+key, parent ) if btn: fgroup[ key ] = fixup( btn.get_attribute( "data-url" ) ) def fixup( val ): #pylint: disable=missing-docstring val = val.replace( webapp.base_url, "{WEBAPP-BASE-URL}" ) val = val.replace( "%7C", "|" ) return val # check the downloads for a scenario that doesn't have any _do_scenario_search( "fighting", [2], webdriver ) assert unload_downloads() is None # check the downloads for a scenario that has some _do_scenario_search( "full", [1], webdriver ) assert unload_downloads() == [ { "screenshot": "{WEBAPP-BASE-URL}/static/images/missing-image.png", "timestamp": "November 14, 2023", "user": "dave", "vasl_setup": "http://test.com/dave:1700000000|vt_setup4.vsav", "vt_setup": "http://test.com/dave:1700000000|vt_setup4.json" }, { "screenshot": "http://test.com/chuck:1600000000|screenshot3.png", "timestamp": "September 13, 2020", "user": "chuck", "vasl_setup": "http://test.com/chuck:1600000000|vt_setup3.vsav" }, { "screenshot": "http://test.com/bob:1500000000|screenshot2.png", "timestamp": "July 14, 2017", "user": "bob", "vt_setup": "http://test.com/bob:1500000000|vt_setup2.json" }, { "screenshot": "http://test.com/alice:1400000000|screenshot1.png", "timestamp": "May 13, 2014", "user": "alice", "vasl_setup": "http://test.com/alice:1400000000|vt_setup1.vsav", "vt_setup": "http://test.com/alice:1400000000|vt_setup1.json" } ] # --------------------------------------------------------------------- def _do_scenario_search( query, expected, webdriver ): """Do a scenario search.""" # find the dialog dlg = find_child( "#scenario-search" ) if not dlg.is_displayed(): select_tab( "scenario" ) btn = find_child( "button.scenario-search" ) ActionChains( webdriver ).key_down( Keys.SHIFT ).click( btn ).perform() dlg = wait_for_elem( 2, "#scenario-search" ) ActionChains( webdriver ).key_up( Keys.SHIFT ).perform() # initialize card = find_child( ".scenario-card", dlg ) seq_no = card.get_attribute( "data-seqNo" ) # do the search and check the results elem = find_child( "input.select2-search__field", dlg ) elem.clear() # IMPORTANT: We can't use send_keys() here because it simulates a key-press for each character in the query, # and the incremental search feature means that we will constantly be loading scenario cards as the results # change, which makes it difficult for us to be able to tell when everything's stopped and it's safe to unload # the scenario card. Instead, we manually load the text box and trigger an event to update the UI. webdriver.execute_script( "arguments[0].value = arguments[1] ; $(arguments[0]).trigger('input')", elem, query ) def check_search_results(): #pylint: disable=missing-docstring results = _unload_search_results() return [ r[0] for r in results ] == [ str(e) for e in expected ] wait_for( 2, check_search_results ) # wait for the scenario card to finish loading # NOTE: We do this here since the typical use case is to search for something, then check what was found. wait_for( 2, lambda: card.get_attribute( "data-seqNo" ) != seq_no ) return dlg def _unload_search_results(): """Unload the current search results.""" results = [] for sr in find_children( "#scenario-search .select2-results .search-result" ): results.append( ( sr.get_attribute("data-id"), sr ) ) return results def _unload_scenario_card(): #pylint: disable=too-many-branches,too-many-locals """Unload the scenario card.""" # initialize card = wait_for( 2, _find_scenario_card ) results = {} # unload the basic text content def unload_text_field( key, sel, trim_prefix=None, trim_postfix=None, trim_parens=False ): """Unload a text field from the scenario card.""" elem = find_child( sel, card ) if not elem: return val = elem.text.strip() if val: if trim_parens: assert val.startswith( "(" ) and val.endswith( ")" ) val = val[1:-1] if trim_prefix: assert val.startswith( trim_prefix ) val = val[ len(trim_prefix) : ] if trim_postfix: assert val.endswith( trim_postfix ) val = val[ : -len(trim_postfix) ] val = val.strip() if val: results[ key ] = val def unload_attr( key, sel, attr ): """Unload a element's attribute from the scenario card.""" elem = find_child( sel, card ) if not elem: return results[ key ] = elem.get_attribute( attr ) unload_text_field( "scenario_name", ".scenario-name" ) unload_attr( "scenario_url", ".scenario-name a", "href" ) unload_text_field( "scenario_id", ".scenario-id", trim_parens=True ) unload_text_field( "scenario_location", ".scenario-location" ) unload_text_field( "scenario_date", ".scenario-date", trim_parens=True ) unload_text_field( "theater", ".info .theater" ) unload_text_field( "turn_count", ".info .turn-count", trim_postfix="turns" ) unload_text_field( "playing_time", ".info .playing-time" ) unload_text_field( "designer", ".designer", trim_prefix="Designer:" ) unload_text_field( "publication", ".publication" ) unload_attr( "publication_url", ".publication a", "href" ) unload_text_field( "publication_date", ".publication-date", trim_parens=True ) unload_text_field( "publisher", ".publisher", trim_parens=True ) unload_attr( "publisher_url", ".publisher a", "href" ) unload_text_field( "prev_publication", ".prev-publication", trim_prefix="Previously:" ) unload_text_field( "revised_publication", ".revised-publication", trim_prefix="Revised:" ) unload_text_field( "map_url", ".map" ) # nb: we don't show a real map in test mode unload_text_field( "overview", ".overview" ) unload_text_field( "defender_name", ".defender .name" ) unload_text_field( "defender_desc", ".defender .desc" ) unload_text_field( "attacker_name", ".attacker .name" ) unload_text_field( "attacker_desc", ".attacker .desc" ) unload_text_field( "boards", ".boards", trim_prefix="Boards:" ) unload_text_field( "overlays", ".overlays", trim_prefix="Overlays:" ) unload_text_field( "extra_rules", ".extra-rules", trim_prefix="Rules:" ) # unload the balance graphs balances = _unload_balance_graphs( card ) if balances: results[ "balances" ] = balances # FUDGE! We just show the lat/long in test mode, not a real map, so we have to remove it # from the overview content. if "overview" in results and "map_url" in results: results["overview"] = results["overview"].replace( results["map_url"], "" ).strip() # unload the icons icons = set( c.get_attribute( "src" ) for c in find_children( ".info .icons img", card ) ) if icons: results[ "icons" ] = sorted( os.path.split(i)[1] for i in icons ) # unload the OBA info oba = find_child( ".player-info .oba", card ) if oba and oba.is_displayed(): oba_info = [] for player in ["defender","attacker"]: row = find_child( ".{}".format( player ), oba ) if not row: oba_info.append( None ) continue oba_info.append( [ find_child( ".name", row ).text, find_child( ".black", row ).text, find_child( ".red", row ).text ] ) comments = find_child( ".{} .comments".format(player), oba ).text if comments: oba_info[-1].extend( comments.split( "\n" ) ) elem = find_child( ".date-warning", oba ) if elem.is_displayed(): oba_info.append( elem.text ) results[ "oba" ] = oba_info # unload any map preview images btn = find_child( ".map-previews", card ) if btn and btn.is_displayed(): btn.click() imgs = find_children( ".lg .lg-thumb-item img" ) if not imgs: # NOTE: If there is only one image, no thumbnails are shown - just use the main image imgs = [ find_child( ".lg .lg-image" ) ] urls = [ e.get_attribute( "src" ) for e in imgs ] results[ "map_previews" ] = [ os.path.basename( u ).replace( "%7C", "|" ) for u in urls ] find_child( ".lg .lg-close" ).click() # unload any errata def get_source( val ): #pylint: disable=missing-docstring assert val.startswith( "[" ) and val.endswith( "]" ) return val[1:-1] elems1 = find_children( ".errata .text", card ) elems2 = find_children( ".errata .source", card ) assert len(elems1) == len(elems2) if len(elems1) > 0: results[ "errata" ] = [ [ e1.text, get_source(e2.text) ] for e1,e2 in zip(elems1,elems2) ] return results def _unload_balance_graphs( parent ): """Unload balance graph(s).""" def get_player_no( elem ): """Figure out what player an element belongs to.""" # FUDGE! Selenium doesn't seem to let us select elements using things like ".wins.player1", # so we have to iterate over all ".wins" elements, and figure out which player each one belongs to :-/ classes = elem.get_attribute( "class" ) if "player1" in classes: return 0 elif "player2" in classes: return 1 else: assert False return -1 # unload the balance graphs balances = {} balance_graphs = find_children( ".balance-graph", parent ) or [] for bgraph in balance_graphs: if not bgraph.is_displayed(): continue balance = [ {}, {} ] for elem in find_children( ".player", bgraph ): balance[ get_player_no(elem) ][ "name" ] = elem.text for elem in find_children( ".wins", bgraph ): wins = elem.text assert wins.startswith( "(" ) and wins.endswith( ")" ) balance[ get_player_no(elem) ][ "wins" ] = int( wins[1:-1] ) for elem in find_children( ".progressbar", bgraph ): percentage = int( elem.get_attribute( "aria-valuenow" ) ) player_no = get_player_no( elem ) if player_no == 0: percentage = 100 - percentage balance[ player_no ][ "percentage" ] = percentage classes = [ c for c in get_css_classes(bgraph) if c != "balance-graph" ] assert len(classes) == 1 balances[ classes[0] ] = balance return balances def _check_warnings( expected, expected2 ): """Check any import warnings being shown.""" def do_check_warnings(): #pylint: """Get import warnings.""" warnings = [ c.get_attribute( "name" ) for c in find_children( "#scenario-search .warnings input[type='checkbox']" ) ] warnings2 = [ c.text for c in find_children( "#scenario-search .warnings .warning2" ) ] return warnings == expected and warnings2 == expected2 wait_for( 2, do_check_warnings ) def _get_scenario_info(): """Open the scenario info and unload the information.""" btn = find_child( "button.scenario-search" ) assert find_child( "img", btn ).get_attribute( "src" ).endswith( "/info.gif" ) btn.click() wait_for_elem( 2, "#scenario-info-dialog" ) card = _unload_scenario_card() btn = find_children( ".ui-dialog .ui-dialog-buttonpane button" )[0] assert btn.text == "OK" btn.click() return card def _unlink_scenario(): """Unlink the scenario from the ASL Scenario Archive.""" find_child( "button.scenario-search" ).click() wait_for_elem( 2, "#scenario-info-dialog" ) btn = find_children( ".ui-dialog .ui-dialog-buttonpane button" )[1] assert btn.text == "Unlink" btn.click() def _import_scenario_and_confirm( dlg ): """Import a scenario, confirming any warnings.""" _click_import_button( dlg ) btn = wait_for_elem( 2, "button.confirm-import", dlg ) btn.click() wait_for( 2, lambda: not dlg.is_displayed() ) def _find_scenario_card(): """Find the currently-displayed scenario card.""" if find_child( "#scenario-search" ).is_displayed(): return find_child( "#scenario-search .scenario-card" ) if find_child( "#scenario-info-dialog" ).is_displayed(): return find_child( "#scenario-info-dialog .scenario-card" ) return None def _click_import_button( dlg ): """Click the "import scenario" button, and confirm the action (if necessary).""" find_child( "button.import", dlg ).click() if find_child( "#ask" ).is_displayed(): click_dialog_button( "OK" )