From 44566659bf255f582f756a21cf022c42163eeda3 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 16:17:36 -0700 Subject: [PATCH 01/21] Added IDs to UI components --- .../Input/distributionParameters/distributionMain.py | 3 +++ .../impactx/dashboard/Input/inputParameters/inputMain.py | 5 +++++ .../dashboard/Input/latticeConfiguration/latticeMain.py | 4 ++++ src/python/impactx/dashboard/Input/trameFunctions.py | 2 +- src/python/impactx/dashboard/Toolbar/toolbarMain.py | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py index b57e16fe7..fc46a1dc6 100644 --- a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py +++ b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py @@ -175,6 +175,7 @@ def card(): with vuetify.VCol(cols=8): vuetify.VCombobox( label="Select Distribution", + id="selected_distribution", v_model=("selectedDistribution",), items=("listOfDistributions",), dense=True, @@ -182,6 +183,7 @@ def card(): with vuetify.VCol(cols=4): vuetify.VSelect( v_model=("selectedDistributionType",), + id="selected_distribution_type", label="Type", items=(["Native", "Twiss"],), # change=(ctrl.kin_energy_unit_change, "[$event]"), @@ -199,6 +201,7 @@ def card(): ): vuetify.VTextField( label=("parameter.parameter_name",), + id=("parameter.parameter_name",), v_model=("parameter.parameter_default_value",), change=( ctrl.updateDistributionParameters, diff --git a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py index 52e7e63ba..25245c6bd 100644 --- a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py +++ b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py @@ -93,6 +93,7 @@ def card(self): vuetify.VCombobox( v_model=("particle_shape",), label="Particle Shape", + id="particle_shape", items=([1, 2, 3],), dense=True, ) @@ -101,6 +102,7 @@ def card(self): vuetify.VTextField( v_model=("npart",), label="Number of Particles", + id="npart", error_messages=("npart_validation",), change=( ctrl.on_input_change, @@ -114,6 +116,7 @@ def card(self): vuetify.VTextField( v_model=("kin_energy",), label="Kinetic Energy", + id="kin_energy", error_messages=("kin_energy_validation",), change=( ctrl.on_input_change, @@ -127,6 +130,7 @@ def card(self): vuetify.VSelect( v_model=("kin_energy_unit",), label="Unit", + id="kin_energy_unit", items=(["meV", "eV", "keV", "MeV", "GeV", "TeV"],), change=(ctrl.kin_energy_unit_change, "[$event]"), dense=True, @@ -135,6 +139,7 @@ def card(self): with vuetify.VCol(cols=8, classes="py-0"): vuetify.VTextField( label="Bunch Charge", + id="bunch_charge_C", v_model=("bunch_charge_C",), error_messages=("bunch_charge_C_validation",), change=( diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index 5371042c2..1645836cc 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -253,6 +253,7 @@ def card(): with vuetify.VCol(cols=8): vuetify.VCombobox( label="Select Accelerator Lattice", + id="selected_lattice", v_model=("selectedLattice", None), items=("listOfLatticeElements",), error_messages=("isSelectedLatticeListEmpty",), @@ -262,6 +263,7 @@ def card(): with vuetify.VCol(cols="auto"): vuetify.VBtn( "ADD", + id="add_button", color="primary", dense=True, classes="mr-2", @@ -270,6 +272,7 @@ def card(): with vuetify.VCol(cols="auto"): vuetify.VBtn( "CLEAR", + id="clear_button", color="secondary", dense=True, classes="mr-2", @@ -337,6 +340,7 @@ def card(): ): vuetify.VTextField( label=("parameter.parameter_name",), + id=("parameter.parameter_name",), v_model=( "parameter.parameter_default_value", ), diff --git a/src/python/impactx/dashboard/Input/trameFunctions.py b/src/python/impactx/dashboard/Input/trameFunctions.py index 599e00c03..a423389cc 100644 --- a/src/python/impactx/dashboard/Input/trameFunctions.py +++ b/src/python/impactx/dashboard/Input/trameFunctions.py @@ -40,4 +40,4 @@ def create_route(route_title, mdi_icon): with vuetify.VListItemIcon(): vuetify.VIcon(mdi_icon) with vuetify.VListItemContent(): - vuetify.VListItemTitle(route_title) + vuetify.VListItemTitle(route_title, id=f"{route_title}_route") diff --git a/src/python/impactx/dashboard/Toolbar/toolbarMain.py b/src/python/impactx/dashboard/Toolbar/toolbarMain.py index ff5894770..63a10d6de 100644 --- a/src/python/impactx/dashboard/Toolbar/toolbarMain.py +++ b/src/python/impactx/dashboard/Toolbar/toolbarMain.py @@ -29,6 +29,7 @@ def plot_options(): v_model=("active_plot", "1D plots over s"), items=("plot_options",), label="Select plot to view", + id="select_plot", hide_details=True, dense=True, style="max-width: 250px", @@ -39,6 +40,7 @@ def plot_options(): def run_simulation_button(): vuetify.VBtn( "Run Simulation", + id="run_simulation_button", style="background-color: #00313C; color: white; margin: 0 20px;", click=ctrl.run_simulation, disabled=("disableRunSimulationButton", True), From f4219ca5e27f8466ec37519fc2ab33d4cb09c899 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 19:16:21 -0700 Subject: [PATCH 02/21] Added some more IDs to UI components --- .../dashboard/Input/latticeConfiguration/latticeMain.py | 4 +++- src/python/impactx/dashboard/__main__.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index 1645836cc..05e45ea61 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -282,6 +282,7 @@ def card(): vuetify.VIcon( "mdi-cog", click="showDialog_settings = true", + id="lattice_settings_icon", ) with vuetify.VRow(): with vuetify.VCol(): @@ -340,7 +341,7 @@ def card(): ): vuetify.VTextField( label=("parameter.parameter_name",), - id=("parameter.parameter_name",), + id=("parameter.parameter_name + index",), v_model=( "parameter.parameter_default_value", ), @@ -424,6 +425,7 @@ def dialog_lattice_settings(): with vuetify.VCol(no_gutters=True): vuetify.VTextField( v_model=("nsliceDefaultValue",), + id="nslice_default_value", change=( ctrl.nsliceDefaultChange, "['nslice', $event]", diff --git a/src/python/impactx/dashboard/__main__.py b/src/python/impactx/dashboard/__main__.py index 285c3fc18..5b8c9a194 100644 --- a/src/python/impactx/dashboard/__main__.py +++ b/src/python/impactx/dashboard/__main__.py @@ -55,7 +55,7 @@ # GUI # ----------------------------------------------------------------------------- def init_terminal(): - with xterm.XTerm(v_if="$route.path == '/Run'") as term: + with xterm.XTerm(v_if="$route.path == '/Run'", id="xterm_component") as term: ctrl.terminal_print = term.writeln From 7f3bbbbf8837f9f6c718d010216f1145354a71a1 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 19:18:31 -0700 Subject: [PATCH 03/21] Added 'close' button in lattice settings Contributes to unit testing by applying a UI component to exit the dialog box and allowing selenium to continue testing the application --- .../Input/latticeConfiguration/latticeMain.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index 05e45ea61..c9c832491 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -437,3 +437,13 @@ def dialog_lattice_settings(): style="max-width: 75px", classes="ma-0 pa-0", ) + vuetify.VDivider() + with vuetify.VCardActions(): + vuetify.VSpacer() + vuetify.VBtn( + "Close", + id="lattice_settings_close", + color="primary", + text=True, + click="showDialog_settings = false", + ) From b0670425c9a846b1656a1176cee863f87f596fdb Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 20:19:33 -0700 Subject: [PATCH 04/21] Removed functionality that converted user input Initially these were added to convert user input to the correct type in order to be compatible with simulation. However, upon commenting these out for the sake of unit testing compatibility, it was found that the simulation is still functional with user inputs and does not need any conversion of user values. There is potential for underlying problems that have yet to be found. --- .../dashboard/Input/distributionParameters/distributionMain.py | 1 - .../impactx/dashboard/Input/latticeConfiguration/latticeMain.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py index fc46a1dc6..f66101da1 100644 --- a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py +++ b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py @@ -139,7 +139,6 @@ def on_distribution_type_change(**kwargs): @ctrl.add("updateDistributionParameters") def on_distribution_parameter_change(parameter_name, parameter_value, parameter_type): - parameter_value, input_type = generalFunctions.determine_input_type(parameter_value) error_message = generalFunctions.validate_against(parameter_value, parameter_type) update_distribution_parameters(parameter_name, parameter_value, error_message) diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index c9c832491..144610daf 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -165,7 +165,6 @@ def on_add_lattice_element_click(): def on_lattice_element_parameter_change( index, parameter_name, parameter_value, parameter_type ): - parameter_value, input_type = generalFunctions.determine_input_type(parameter_value) error_message = generalFunctions.validate_against(parameter_value, parameter_type) update_latticeElement_parameters( From fd50751157b56744d133d1b44d57af1df4e920c0 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 20:36:57 -0700 Subject: [PATCH 05/21] Added unit test for running FODO simulation on dashboard --- tests/python/dashboard/test_dashboard.py | 68 ++++++++++++++++++++++++ tests/python/dashboard/util.py | 22 ++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/python/dashboard/test_dashboard.py create mode 100644 tests/python/dashboard/util.py diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py new file mode 100644 index 000000000..ddafae8d4 --- /dev/null +++ b/tests/python/dashboard/test_dashboard.py @@ -0,0 +1,68 @@ +from seleniumbase import SB +from util import set_input_value, wait_for_ready + + +def test_simulation(): + """ + This test runs the FODO example on the dashboard and verifies + that the simulation has ran successfully. + """ + + with SB() as sb: + url = "http://localhost:8080/index.html#/Input" + sb.open(url) + + wait_for_ready(sb, 60) + + # Adjust beam properties + sb.click("#particle_shape") + sb.click("div.v-list-item:nth-of-type(2)") + set_input_value(sb, "npart", 10000) + set_input_value(sb, "kin_energy", 2.0e3) + set_input_value(sb, "bunch_charge_C", 1.0e-9) + + # Adjust beam distribution + set_input_value(sb, "selected_distribution", "Waterbag") + set_input_value(sb, "lambdaX", 3.9984884770e-5) + set_input_value(sb, "lambdaY", 3.9984884770e-5) + set_input_value(sb, "lambdaT", 1.0e-3) + set_input_value(sb, "lambdaPx", 2.6623538760e-5) + set_input_value(sb, "lambdaPy", 2.6623538760e-5) + set_input_value(sb, "lambdaPt", 2.0e-3) + set_input_value(sb, "muxpx", -0.846574929020762) + set_input_value(sb, "muypy", 0.846574929020762) + set_input_value(sb, "mutpt", 0.0) + + # Adjust lattice configuration + sb.click("#lattice_settings_icon") + sb.sleep(1) + set_input_value(sb, "nslice_default_value", 25) + sb.click("#lattice_settings_close") + sb.click("#clear_button") + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds0", 0.25) + set_input_value(sb, "selected_lattice", "Quad") + sb.click("#add_button") + set_input_value(sb, "ds1", 1.0) + set_input_value(sb, "k1", 1.0) + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds2", 0.5) + set_input_value(sb, "selected_lattice", "Quad") + sb.click("#add_button") + set_input_value(sb, "ds3", 1.0) + set_input_value(sb, "k3", -1.0) + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds4", 0.25) + + # Run simulation + sb.click("#Run_route") + sb.sleep(1) + sb.click("#run_simulation_button") + sb.sleep(7) + + # Check if "Simulation complete" message is printed + xterm_content = sb.get_text("#xterm_component") + assert "Simulation complete." in xterm_content diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py new file mode 100644 index 000000000..6aa9ac068 --- /dev/null +++ b/tests/python/dashboard/util.py @@ -0,0 +1,22 @@ +def wait_for_ready(sb, timeout=60): + for i in range(timeout): + print(f"wait_for_ready {i}") + if sb.is_element_present(".trame__loader"): + sb.sleep(1) + else: + print("Ready") + return + + +def set_input_value(sb, element_id, value): + """ + Function to clear, update, and trigger a change event on an input field by ID. + """ + selector = f"#{element_id}" + sb.clear(selector) + + if not isinstance(value, str): + value = str(value) + + sb.update_text(selector, value) + sb.send_keys(selector, "\n") From 9fd2b545e4ab1843937044690c67b309b6b5f4bb Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 10 Aug 2024 23:58:12 -0700 Subject: [PATCH 06/21] Added subprocess to start up server with pytest --- tests/python/dashboard/test_dashboard.py | 115 ++++++++++++----------- tests/python/dashboard/util.py | 16 ++++ 2 files changed, 77 insertions(+), 54 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index ddafae8d4..6b3ab4d56 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -1,6 +1,6 @@ from seleniumbase import SB -from util import set_input_value, wait_for_ready - +from util import set_input_value, start_dashboard, wait_for_ready +import time def test_simulation(): """ @@ -8,61 +8,68 @@ def test_simulation(): that the simulation has ran successfully. """ - with SB() as sb: - url = "http://localhost:8080/index.html#/Input" - sb.open(url) + app_process = start_dashboard() + time.sleep(10) + + try: + with SB() as sb: + url = "http://localhost:8080/index.html#/Input" + sb.open(url) + + wait_for_ready(sb, 60) - wait_for_ready(sb, 60) + # Adjust beam properties + sb.click("#particle_shape") + sb.click("div.v-list-item:nth-of-type(2)") + set_input_value(sb, "npart", 10000) + set_input_value(sb, "kin_energy", 2.0e3) + set_input_value(sb, "bunch_charge_C", 1.0e-9) - # Adjust beam properties - sb.click("#particle_shape") - sb.click("div.v-list-item:nth-of-type(2)") - set_input_value(sb, "npart", 10000) - set_input_value(sb, "kin_energy", 2.0e3) - set_input_value(sb, "bunch_charge_C", 1.0e-9) + # Adjust beam distribution + set_input_value(sb, "selected_distribution", "Waterbag") + set_input_value(sb, "lambdaX", 3.9984884770e-5) + set_input_value(sb, "lambdaY", 3.9984884770e-5) + set_input_value(sb, "lambdaT", 1.0e-3) + set_input_value(sb, "lambdaPx", 2.6623538760e-5) + set_input_value(sb, "lambdaPy", 2.6623538760e-5) + set_input_value(sb, "lambdaPt", 2.0e-3) + set_input_value(sb, "muxpx", -0.846574929020762) + set_input_value(sb, "muypy", 0.846574929020762) + set_input_value(sb, "mutpt", 0.0) - # Adjust beam distribution - set_input_value(sb, "selected_distribution", "Waterbag") - set_input_value(sb, "lambdaX", 3.9984884770e-5) - set_input_value(sb, "lambdaY", 3.9984884770e-5) - set_input_value(sb, "lambdaT", 1.0e-3) - set_input_value(sb, "lambdaPx", 2.6623538760e-5) - set_input_value(sb, "lambdaPy", 2.6623538760e-5) - set_input_value(sb, "lambdaPt", 2.0e-3) - set_input_value(sb, "muxpx", -0.846574929020762) - set_input_value(sb, "muypy", 0.846574929020762) - set_input_value(sb, "mutpt", 0.0) + # Adjust lattice configuration + sb.click("#lattice_settings_icon") + sb.sleep(1) + set_input_value(sb, "nslice_default_value", 25) + sb.click("#lattice_settings_close") + sb.click("#clear_button") + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds0", 0.25) + set_input_value(sb, "selected_lattice", "Quad") + sb.click("#add_button") + set_input_value(sb, "ds1", 1.0) + set_input_value(sb, "k1", 1.0) + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds2", 0.5) + set_input_value(sb, "selected_lattice", "Quad") + sb.click("#add_button") + set_input_value(sb, "ds3", 1.0) + set_input_value(sb, "k3", -1.0) + set_input_value(sb, "selected_lattice", "Drift") + sb.click("#add_button") + set_input_value(sb, "ds4", 0.25) - # Adjust lattice configuration - sb.click("#lattice_settings_icon") - sb.sleep(1) - set_input_value(sb, "nslice_default_value", 25) - sb.click("#lattice_settings_close") - sb.click("#clear_button") - set_input_value(sb, "selected_lattice", "Drift") - sb.click("#add_button") - set_input_value(sb, "ds0", 0.25) - set_input_value(sb, "selected_lattice", "Quad") - sb.click("#add_button") - set_input_value(sb, "ds1", 1.0) - set_input_value(sb, "k1", 1.0) - set_input_value(sb, "selected_lattice", "Drift") - sb.click("#add_button") - set_input_value(sb, "ds2", 0.5) - set_input_value(sb, "selected_lattice", "Quad") - sb.click("#add_button") - set_input_value(sb, "ds3", 1.0) - set_input_value(sb, "k3", -1.0) - set_input_value(sb, "selected_lattice", "Drift") - sb.click("#add_button") - set_input_value(sb, "ds4", 0.25) + # Run simulation + sb.click("#Run_route") + sb.sleep(1) + sb.click("#run_simulation_button") + sb.sleep(7) - # Run simulation - sb.click("#Run_route") - sb.sleep(1) - sb.click("#run_simulation_button") - sb.sleep(7) + # Check if "Simulation complete" message is printed + xterm_content = sb.get_text("#xterm_component") + assert "Simulation complete." in xterm_content - # Check if "Simulation complete" message is printed - xterm_content = sb.get_text("#xterm_component") - assert "Simulation complete." in xterm_content + finally: + app_process.terminate() diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 6aa9ac068..895820451 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -1,3 +1,6 @@ +import os +import subprocess + def wait_for_ready(sb, timeout=60): for i in range(timeout): print(f"wait_for_ready {i}") @@ -20,3 +23,16 @@ def set_input_value(sb, element_id, value): sb.update_text(selector, value) sb.send_keys(selector, "\n") + +def start_dashboard(): + """ + Function which starts up impactx-dashboard server. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + working_directory = os.path.join(script_dir, "../../../src/python/impactx") + working_directory = os.path.normpath(working_directory) + + return subprocess.Popen( + ["python", "-m", "dashboard"], + cwd=working_directory + ) From 583f227aa9a2413078aae4fe9dde98dae70a4f41 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sun, 11 Aug 2024 18:50:36 -0700 Subject: [PATCH 07/21] Attempt to fix flaky test --- src/python/impactx/dashboard/__main__.py | 2 +- tests/python/dashboard/test_dashboard.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/python/impactx/dashboard/__main__.py b/src/python/impactx/dashboard/__main__.py index 5b8c9a194..9ab37bd28 100644 --- a/src/python/impactx/dashboard/__main__.py +++ b/src/python/impactx/dashboard/__main__.py @@ -55,7 +55,7 @@ # GUI # ----------------------------------------------------------------------------- def init_terminal(): - with xterm.XTerm(v_if="$route.path == '/Run'", id="xterm_component") as term: + with xterm.XTerm(v_show="$route.path == '/Run'", id="xterm_component") as term: ctrl.terminal_print = term.writeln diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 6b3ab4d56..72b4b695f 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -65,11 +65,10 @@ def test_simulation(): sb.click("#Run_route") sb.sleep(1) sb.click("#run_simulation_button") - sb.sleep(7) + sb.sleep(25) # Check if "Simulation complete" message is printed xterm_content = sb.get_text("#xterm_component") assert "Simulation complete." in xterm_content - finally: app_process.terminate() From f7e0e831b3277d74a05d712ac6627e534f638e28 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sun, 11 Aug 2024 19:33:13 -0700 Subject: [PATCH 08/21] Add seleniumbase dependency in CI workflows for testing --- .github/workflows/macos.yml | 2 +- .github/workflows/stubs.yml | 3 ++- .github/workflows/tooling.yml | 1 + .github/workflows/windows.yml | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0eaab8e26..f1d3a639a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -40,7 +40,7 @@ jobs: source py-venv/bin/activate python3 -m pip install --upgrade pip python3 -m pip install --upgrade build packaging setuptools wheel pytest - python3 -m pip install --upgrade pytest + python3 -m pip install --upgrade pytest seleniumbase python3 -m pip install --upgrade -r requirements_mpi.txt python3 -m pip install --upgrade -r examples/requirements.txt set -e diff --git a/.github/workflows/stubs.yml b/.github/workflows/stubs.yml index 784134bdd..2fd8e89c0 100644 --- a/.github/workflows/stubs.yml +++ b/.github/workflows/stubs.yml @@ -39,7 +39,7 @@ jobs: run: | .github/workflows/dependencies/gcc-openmpi.sh python3 -m pip install -U pip setuptools wheel - python3 -m pip install -U pip mpi4py pytest pybind11-stubgen pre-commit + python3 -m pip install -U pip mpi4py pytest pybind11-stubgen pre-commit seleniumbase - name: Set Up Cache uses: actions/cache@v4 @@ -84,6 +84,7 @@ jobs: run: | mpiexec -np 1 python3 -m pytest tests/python/ + - uses: stefanzweifel/git-auto-commit-action@v5 name: Commit Updated Stub Files if: github.event_name == 'push' && github.repository == 'ECP-WarpX/impactx' && github.ref == 'refs/heads/development' diff --git a/.github/workflows/tooling.yml b/.github/workflows/tooling.yml index 37e164df8..117f00c49 100644 --- a/.github/workflows/tooling.yml +++ b/.github/workflows/tooling.yml @@ -17,6 +17,7 @@ jobs: - name: install dependencies run: | .github/workflows/dependencies/clang-san-openmpi.sh + python3 -m pip install --upgrade seleniumbase - name: CCache Cache uses: actions/cache@v4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9f63d6294..fe742cb40 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -57,7 +57,7 @@ jobs: $env:HDF5_USE_STATIC_LIBRARIES = "ON" python3 -m pip install -U pip - python3 -m pip install -U build packaging setuptools wheel pytest + python3 -m pip install -U build packaging setuptools wheel pytest seleniumbase python3 -m pip install -U -r requirements.txt python3 -m pip install -U -r examples/requirements.txt python3 -m pip install -U openPMD-validator @@ -147,7 +147,7 @@ jobs: set "HDF5_USE_STATIC_LIBRARIES=ON" python3 -m pip install -U pip - python3 -m pip install -U build packaging setuptools wheel pytest + python3 -m pip install -U build packaging setuptools wheel pytest seleniumbase python3 -m pip install -U -r requirements.txt python3 -m pip install -U -r examples/requirements.txt python3 -m pip install -U openPMD-validator From e5f9e94f176f9051d74172d75d3cb568cfdccfc6 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sun, 11 Aug 2024 20:03:44 -0700 Subject: [PATCH 09/21] Added dashboard dependencies in stubs.yml --- .github/workflows/stubs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/stubs.yml b/.github/workflows/stubs.yml index 2fd8e89c0..b817d5876 100644 --- a/.github/workflows/stubs.yml +++ b/.github/workflows/stubs.yml @@ -41,6 +41,10 @@ jobs: python3 -m pip install -U pip setuptools wheel python3 -m pip install -U pip mpi4py pytest pybind11-stubgen pre-commit seleniumbase + - name: Dashboard Dependencies + run: | + python3 -m pip install -U trame trame-matplotlib trame-plotly trame-router trame-xterm trame-vuetify + - name: Set Up Cache uses: actions/cache@v4 with: From 91fa693e6ee4011362fe04f515494100fa3c9b39 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sun, 11 Aug 2024 20:16:31 -0700 Subject: [PATCH 10/21] Added missing dashboard dependencies to stubs.yml --- .github/workflows/stubs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stubs.yml b/.github/workflows/stubs.yml index b817d5876..4acd97ef4 100644 --- a/.github/workflows/stubs.yml +++ b/.github/workflows/stubs.yml @@ -43,7 +43,7 @@ jobs: - name: Dashboard Dependencies run: | - python3 -m pip install -U trame trame-matplotlib trame-plotly trame-router trame-xterm trame-vuetify + python3 -m pip install -U matplotlib plotly trame trame-matplotlib trame-plotly trame-router trame-xterm trame-vuetify - name: Set Up Cache uses: actions/cache@v4 From 703d73c9a29010b8eca972d250522031adbcee17 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Mon, 12 Aug 2024 10:03:15 -0700 Subject: [PATCH 11/21] Update CI & Requirements Files --- .github/workflows/dependencies/clang-san-openmpi.sh | 4 +++- .github/workflows/dependencies/clang-tidy.sh | 4 +++- .github/workflows/dependencies/gcc-openmpi.sh | 4 +++- .github/workflows/dependencies/gcc.sh | 4 +++- .github/workflows/macos.yml | 5 +++-- .github/workflows/stubs.yml | 8 +------- .github/workflows/tooling.yml | 1 - .github/workflows/windows.yml | 8 ++++++-- tests/python/requirements.txt | 1 + 9 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/dependencies/clang-san-openmpi.sh b/.github/workflows/dependencies/clang-san-openmpi.sh index e4d699bed..51322a8d5 100755 --- a/.github/workflows/dependencies/clang-san-openmpi.sh +++ b/.github/workflows/dependencies/clang-san-openmpi.sh @@ -30,9 +30,11 @@ sudo apt-get install -y \ python3 -m pip install -U pip python3 -m pip install -U build packaging setuptools wheel -python3 -m pip install -U cmake pytest +python3 -m pip install -U cmake python3 -m pip install -U -r requirements_mpi.txt +python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt python3 -m pip install -U -r examples/requirements.txt +python3 -m pip install -U -r tests/python/requirements.txt # cmake-easyinstall # diff --git a/.github/workflows/dependencies/clang-tidy.sh b/.github/workflows/dependencies/clang-tidy.sh index 134f3a7a7..b4695e4d8 100755 --- a/.github/workflows/dependencies/clang-tidy.sh +++ b/.github/workflows/dependencies/clang-tidy.sh @@ -29,9 +29,11 @@ sudo apt-get install -y \ python3 -m pip install -U pip python3 -m pip install -U build packaging setuptools wheel -python3 -m pip install -U cmake pytest +python3 -m pip install -U cmake python3 -m pip install -U -r requirements_mpi.txt +python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt python3 -m pip install -U -r examples/requirements.txt +python3 -m pip install -U -r tests/python/requirements.txt # cmake-easyinstall # diff --git a/.github/workflows/dependencies/gcc-openmpi.sh b/.github/workflows/dependencies/gcc-openmpi.sh index ee5159d44..a53e8395c 100755 --- a/.github/workflows/dependencies/gcc-openmpi.sh +++ b/.github/workflows/dependencies/gcc-openmpi.sh @@ -26,8 +26,10 @@ sudo apt-get install -y \ python3 -m pip install -U pip python3 -m pip install -U build packaging setuptools wheel -python3 -m pip install -U cmake pytest +python3 -m pip install -U cmake python3 -m pip install -U -r requirements_mpi.txt +python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt python3 -m pip install -U -r examples/requirements.txt +python3 -m pip install -U -r tests/python/requirements.txt python3 -m pip install -U openPMD-validator diff --git a/.github/workflows/dependencies/gcc.sh b/.github/workflows/dependencies/gcc.sh index 1eb2882a0..c39caa33a 100755 --- a/.github/workflows/dependencies/gcc.sh +++ b/.github/workflows/dependencies/gcc.sh @@ -24,8 +24,10 @@ sudo apt-get install -y \ python3 -m pip install -U pip python3 -m pip install -U build packaging setuptools wheel -python3 -m pip install -U cmake pytest +python3 -m pip install -U cmake python3 -m pip install -U -r requirements.txt +python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt python3 -m pip install -U -r examples/requirements.txt +python3 -m pip install -U -r tests/python/requirements.txt python3 -m pip install -U openPMD-validator diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f1d3a639a..fc22071fc 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -39,10 +39,11 @@ jobs: python3 -m venv py-venv source py-venv/bin/activate python3 -m pip install --upgrade pip - python3 -m pip install --upgrade build packaging setuptools wheel pytest - python3 -m pip install --upgrade pytest seleniumbase + python3 -m pip install --upgrade build packaging setuptools wheel python3 -m pip install --upgrade -r requirements_mpi.txt + python3 -m pip install --upgrade -r src/python/impactx/dashboard/requirements.txt python3 -m pip install --upgrade -r examples/requirements.txt + python3 -m pip install --upgrade -r tests/python/requirements.txt set -e python3 -m pip install --upgrade pipx python3 -m pipx install openPMD-validator diff --git a/.github/workflows/stubs.yml b/.github/workflows/stubs.yml index 4acd97ef4..aae642bbc 100644 --- a/.github/workflows/stubs.yml +++ b/.github/workflows/stubs.yml @@ -38,12 +38,7 @@ jobs: - name: Dependencies run: | .github/workflows/dependencies/gcc-openmpi.sh - python3 -m pip install -U pip setuptools wheel - python3 -m pip install -U pip mpi4py pytest pybind11-stubgen pre-commit seleniumbase - - - name: Dashboard Dependencies - run: | - python3 -m pip install -U matplotlib plotly trame trame-matplotlib trame-plotly trame-router trame-xterm trame-vuetify + python3 -m pip install -U pybind11-stubgen - name: Set Up Cache uses: actions/cache@v4 @@ -88,7 +83,6 @@ jobs: run: | mpiexec -np 1 python3 -m pytest tests/python/ - - uses: stefanzweifel/git-auto-commit-action@v5 name: Commit Updated Stub Files if: github.event_name == 'push' && github.repository == 'ECP-WarpX/impactx' && github.ref == 'refs/heads/development' diff --git a/.github/workflows/tooling.yml b/.github/workflows/tooling.yml index 117f00c49..37e164df8 100644 --- a/.github/workflows/tooling.yml +++ b/.github/workflows/tooling.yml @@ -17,7 +17,6 @@ jobs: - name: install dependencies run: | .github/workflows/dependencies/clang-san-openmpi.sh - python3 -m pip install --upgrade seleniumbase - name: CCache Cache uses: actions/cache@v4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fe742cb40..110321360 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -57,9 +57,11 @@ jobs: $env:HDF5_USE_STATIC_LIBRARIES = "ON" python3 -m pip install -U pip - python3 -m pip install -U build packaging setuptools wheel pytest seleniumbase + python3 -m pip install -U build packaging setuptools wheel python3 -m pip install -U -r requirements.txt python3 -m pip install -U -r examples/requirements.txt + python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt + python3 -m pip install -U -r tests/python/requirements.txt python3 -m pip install -U openPMD-validator - name: Build env: @@ -147,9 +149,11 @@ jobs: set "HDF5_USE_STATIC_LIBRARIES=ON" python3 -m pip install -U pip - python3 -m pip install -U build packaging setuptools wheel pytest seleniumbase + python3 -m pip install -U build packaging setuptools wheel python3 -m pip install -U -r requirements.txt python3 -m pip install -U -r examples/requirements.txt + python3 -m pip install -U -r src/python/impactx/dashboard/requirements.txt + python3 -m pip install -U -r tests/python/requirements.txt python3 -m pip install -U openPMD-validator - name: Build shell: cmd diff --git a/tests/python/requirements.txt b/tests/python/requirements.txt index 1052c9366..318cac888 100644 --- a/tests/python/requirements.txt +++ b/tests/python/requirements.txt @@ -1,2 +1,3 @@ -r ../../examples/requirements.txt pytest +seleniumbase From c94b280b4af7e3cedc351ae1eda55d4bed0d0358 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Mon, 12 Aug 2024 10:37:06 -0700 Subject: [PATCH 12/21] pytest: Graceful Skipping Mark the test as skipped if `seleniumbase` is not available. --- tests/python/dashboard/test_dashboard.py | 10 +++++++++- tests/python/dashboard/util.py | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 72b4b695f..a321366d7 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -1,12 +1,20 @@ -from seleniumbase import SB +import importlib + from util import set_input_value, start_dashboard, wait_for_ready import time +import pytest + + +@pytest.mark.skipif( + importlib.util.find_spec("seleniumbase") is None, reason="seleniumbase is not available" +) def test_simulation(): """ This test runs the FODO example on the dashboard and verifies that the simulation has ran successfully. """ + from seleniumbase import SB app_process = start_dashboard() time.sleep(10) diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 895820451..ac1036927 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -1,6 +1,7 @@ import os import subprocess + def wait_for_ready(sb, timeout=60): for i in range(timeout): print(f"wait_for_ready {i}") @@ -24,6 +25,7 @@ def set_input_value(sb, element_id, value): sb.update_text(selector, value) sb.send_keys(selector, "\n") + def start_dashboard(): """ Function which starts up impactx-dashboard server. From 2e5588c89ce82393e535894acfef1eb7db4a60b9 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 17 Aug 2024 23:30:44 -0700 Subject: [PATCH 13/21] Unit test interacts with phase space projections This indirectly assures user that sim ran successfully. --- src/python/impactx/dashboard/Analyze/plotsMain.py | 2 +- tests/python/dashboard/test_dashboard.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/python/impactx/dashboard/Analyze/plotsMain.py b/src/python/impactx/dashboard/Analyze/plotsMain.py index 4d71ffb86..6df7297fa 100644 --- a/src/python/impactx/dashboard/Analyze/plotsMain.py +++ b/src/python/impactx/dashboard/Analyze/plotsMain.py @@ -228,7 +228,7 @@ def plot(): with vuetify.VCard(style="height: 50vh; width: 150vh;"): with vuetify.VTabs(v_model=("active_tab", 0)): vuetify.VTab("Plot") - vuetify.VTab("Interact") + vuetify.VTab("Interact", id="interact") vuetify.VDivider() with vuetify.VTabsItems(v_model="active_tab"): with vuetify.VTabItem(): diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index a321366d7..9107c644a 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -73,10 +73,16 @@ def test_simulation(): sb.click("#Run_route") sb.sleep(1) sb.click("#run_simulation_button") - sb.sleep(25) + sb.sleep(7) - # Check if "Simulation complete" message is printed - xterm_content = sb.get_text("#xterm_component") - assert "Simulation complete." in xterm_content + sb.wait_for_element("#select_plot", timeout=10) + + # Interact with phase space projection plots + sb.click("#Analyze_route") + sb.sleep(3) + sb.click("#select_plot") + sb.click("div.v-list-item:nth-of-type(2)") + sb.wait_for_element("#interact", timeout=10) + sb.click("#interact") finally: app_process.terminate() From e3fec3984846cbf4970ff7b860ca26b0f9884697 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 17 Aug 2024 23:48:35 -0700 Subject: [PATCH 14/21] time adjustments --- tests/python/dashboard/test_dashboard.py | 12 +++++------- tests/python/dashboard/util.py | 23 ++++++++++++++++++++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 9107c644a..9dda5e8cf 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -1,7 +1,6 @@ import importlib -from util import set_input_value, start_dashboard, wait_for_ready -import time +from util import set_input_value, start_dashboard, wait_for_dashboard_ready, wait_for_ready import pytest @@ -17,14 +16,15 @@ def test_simulation(): from seleniumbase import SB app_process = start_dashboard() - time.sleep(10) + + wait_for_dashboard_ready(app_process, timeout=60) try: with SB() as sb: url = "http://localhost:8080/index.html#/Input" sb.open(url) - wait_for_ready(sb, 60) + wait_for_ready(sb, ".trame__loader", 60) # Adjust beam properties sb.click("#particle_shape") @@ -73,9 +73,7 @@ def test_simulation(): sb.click("#Run_route") sb.sleep(1) sb.click("#run_simulation_button") - sb.sleep(7) - - sb.wait_for_element("#select_plot", timeout=10) + sb.sleep(7) # for simulation to finish # Interact with phase space projection plots sb.click("#Analyze_route") diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index ac1036927..8eb4b99cc 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -2,16 +2,30 @@ import subprocess -def wait_for_ready(sb, timeout=60): +def wait_for_ready(sb, element_name, timeout): + # Code adapted from https://github.com/Kitware/trame-client/blob/master/trame_client/utils/testing.py for i in range(timeout): print(f"wait_for_ready {i}") - if sb.is_element_present(".trame__loader"): + if sb.is_element_present(element_name): sb.sleep(1) else: print("Ready") return +def wait_for_dashboard_ready(process, timeout=60): + """ + Function waits until the dashboard server is ready by checking the process output. + """ + for i in range(timeout): + line = process.stdout.readline() + if line: + print(line, end="") + if "App running at:" in line: + print("Dashboard is ready!") + return + raise Exception("Dashboard did not start correctly.") + def set_input_value(sb, element_id, value): """ Function to clear, update, and trigger a change event on an input field by ID. @@ -36,5 +50,8 @@ def start_dashboard(): return subprocess.Popen( ["python", "-m", "dashboard"], - cwd=working_directory + cwd=working_directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True ) From 7bbf231fbd377ffebb98f9e19c994e486f81992d Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sat, 17 Aug 2024 23:55:22 -0700 Subject: [PATCH 15/21] adjust format --- tests/python/dashboard/test_dashboard.py | 9 ++++----- tests/python/dashboard/util.py | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 9dda5e8cf..c1bf61d31 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -8,19 +8,18 @@ @pytest.mark.skipif( importlib.util.find_spec("seleniumbase") is None, reason="seleniumbase is not available" ) -def test_simulation(): +def test_dashboard(): """ This test runs the FODO example on the dashboard and verifies that the simulation has ran successfully. """ from seleniumbase import SB - app_process = start_dashboard() - - wait_for_dashboard_ready(app_process, timeout=60) - try: with SB() as sb: + app_process = start_dashboard() + wait_for_dashboard_ready(app_process, timeout=60) + url = "http://localhost:8080/index.html#/Input" sb.open(url) diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 8eb4b99cc..b5e4a2848 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -4,6 +4,9 @@ def wait_for_ready(sb, element_name, timeout): # Code adapted from https://github.com/Kitware/trame-client/blob/master/trame_client/utils/testing.py + """ + Function waits until element_name is present. + """ for i in range(timeout): print(f"wait_for_ready {i}") if sb.is_element_present(element_name): From e493debda477db87d325b16e5050a513aaa8fc27 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Sun, 18 Aug 2024 00:09:22 -0700 Subject: [PATCH 16/21] attempt to run headless --- tests/python/dashboard/test_dashboard.py | 2 +- tests/python/dashboard/util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index c1bf61d31..25a7004a0 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -16,7 +16,7 @@ def test_dashboard(): from seleniumbase import SB try: - with SB() as sb: + with SB(headless=True) as sb: app_process = start_dashboard() wait_for_dashboard_ready(app_process, timeout=60) diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index b5e4a2848..77488337f 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -52,7 +52,7 @@ def start_dashboard(): working_directory = os.path.normpath(working_directory) return subprocess.Popen( - ["python", "-m", "dashboard"], + ["python", "-m", "dashboard", "--server"], cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, From 36c79280af2b58a68046b4aa926e67a35e7d3141 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 18 Aug 2024 07:10:18 +0000 Subject: [PATCH 17/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/python/dashboard/test_dashboard.py | 13 +++++++++---- tests/python/dashboard/util.py | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 25a7004a0..8279d2d99 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -1,12 +1,17 @@ import importlib -from util import set_input_value, start_dashboard, wait_for_dashboard_ready, wait_for_ready - import pytest +from util import ( + set_input_value, + start_dashboard, + wait_for_dashboard_ready, + wait_for_ready, +) @pytest.mark.skipif( - importlib.util.find_spec("seleniumbase") is None, reason="seleniumbase is not available" + importlib.util.find_spec("seleniumbase") is None, + reason="seleniumbase is not available", ) def test_dashboard(): """ @@ -72,7 +77,7 @@ def test_dashboard(): sb.click("#Run_route") sb.sleep(1) sb.click("#run_simulation_button") - sb.sleep(7) # for simulation to finish + sb.sleep(7) # for simulation to finish # Interact with phase space projection plots sb.click("#Analyze_route") diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 77488337f..fb30a8772 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -29,6 +29,7 @@ def wait_for_dashboard_ready(process, timeout=60): return raise Exception("Dashboard did not start correctly.") + def set_input_value(sb, element_id, value): """ Function to clear, update, and trigger a change event on an input field by ID. @@ -56,5 +57,5 @@ def start_dashboard(): cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True + universal_newlines=True, ) From 42c59722fe9d22e26d0368d252aab382b07b3439 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Wed, 18 Sep 2024 19:55:45 -0700 Subject: [PATCH 18/21] Fixed UnboundLocalError and PermissionError changed hardcoded 'python' command to 'sys.executable' and also initialized app_process --- tests/python/dashboard/test_dashboard.py | 5 ++++- tests/python/dashboard/util.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 8279d2d99..e51f15f25 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -20,6 +20,8 @@ def test_dashboard(): """ from seleniumbase import SB + app_process = None + try: with SB(headless=True) as sb: app_process = start_dashboard() @@ -87,4 +89,5 @@ def test_dashboard(): sb.wait_for_element("#interact", timeout=10) sb.click("#interact") finally: - app_process.terminate() + if app_process is not None: + app_process.terminate() diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index fb30a8772..04fec1005 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -1,5 +1,6 @@ import os import subprocess +import sys def wait_for_ready(sb, element_name, timeout): @@ -53,7 +54,7 @@ def start_dashboard(): working_directory = os.path.normpath(working_directory) return subprocess.Popen( - ["python", "-m", "dashboard", "--server"], + [sys.executable, "-m", "dashboard", "--server"], cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, From 52c1ddafba8a13f24ec682c171aa1e1bfc1e0544 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Wed, 18 Sep 2024 21:42:54 -0700 Subject: [PATCH 19/21] Replace time.sleep() with alternative Adjusted function to retry upon failure until timeout has reached max time. --- tests/python/dashboard/test_dashboard.py | 18 ++++------- tests/python/dashboard/util.py | 41 ++++++++++++++++++------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index e51f15f25..316e9f9d4 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -2,12 +2,14 @@ import pytest from util import ( + look_for_text, set_input_value, start_dashboard, wait_for_dashboard_ready, wait_for_ready, ) +TIMEOUT = 60 @pytest.mark.skipif( importlib.util.find_spec("seleniumbase") is None, @@ -25,12 +27,12 @@ def test_dashboard(): try: with SB(headless=True) as sb: app_process = start_dashboard() - wait_for_dashboard_ready(app_process, timeout=60) + wait_for_dashboard_ready(app_process, timeout=TIMEOUT) url = "http://localhost:8080/index.html#/Input" sb.open(url) - wait_for_ready(sb, ".trame__loader", 60) + wait_for_ready(sb, ".trame__loader", TIMEOUT) # Adjust beam properties sb.click("#particle_shape") @@ -53,7 +55,6 @@ def test_dashboard(): # Adjust lattice configuration sb.click("#lattice_settings_icon") - sb.sleep(1) set_input_value(sb, "nslice_default_value", 25) sb.click("#lattice_settings_close") sb.click("#clear_button") @@ -77,17 +78,10 @@ def test_dashboard(): # Run simulation sb.click("#Run_route") - sb.sleep(1) sb.click("#run_simulation_button") - sb.sleep(7) # for simulation to finish - # Interact with phase space projection plots - sb.click("#Analyze_route") - sb.sleep(3) - sb.click("#select_plot") - sb.click("div.v-list-item:nth-of-type(2)") - sb.wait_for_element("#interact", timeout=10) - sb.click("#interact") + assert look_for_text(sb, "#xterm_component", "Simulation complete.", timeout=TIMEOUT), "'Simulation compelte.' not found." + finally: if app_process is not None: app_process.terminate() diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 04fec1005..907ad6c81 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -1,12 +1,12 @@ import os import subprocess import sys +from selenium.common.exceptions import ElementNotInteractableException, JavascriptException +import time - -def wait_for_ready(sb, element_name, timeout): - # Code adapted from https://github.com/Kitware/trame-client/blob/master/trame_client/utils/testing.py +def wait_for_ready(sb, element_name, timeout=10): """ - Function waits until element_name is present. + Waits until the specified element is present in the DOM. """ for i in range(timeout): print(f"wait_for_ready {i}") @@ -31,19 +31,40 @@ def wait_for_dashboard_ready(process, timeout=60): raise Exception("Dashboard did not start correctly.") -def set_input_value(sb, element_id, value): +def set_input_value(sb, element_id, value, timeout=60): """ Function to clear, update, and trigger a change event on an input field by ID. + Waits until the element is interactable before performing actions. """ + selector = f"#{element_id}" - sb.clear(selector) + end_time = time.time() + timeout + + while True: + try: + sb.clear(selector) + sb.update_text(selector, value) + sb.send_keys(selector, "\n") + break + except (ElementNotInteractableException, JavascriptException): + if time.time() > end_time: + raise Exception(f"Element {selector} not interactable after {timeout} seconds.") - if not isinstance(value, str): - value = str(value) +def look_for_text(sb, element_id, text_to_look_for, timeout): + """ + Function which repeatedly checks for specific text every second until it appears or the timeout is reached. + """ - sb.update_text(selector, value) - sb.send_keys(selector, "\n") + start_time = time.time() + while time.time() - start_time < timeout: + try: + if sb.is_text_visible(text_to_look_for, element_id): + return True + except Exception as e: + print(f"Error checking for text: {e}") + time.sleep(1) + return False def start_dashboard(): """ From 8a1b78722864cb08c9d6d1e5fe7e374fec9f84eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 04:45:47 +0000 Subject: [PATCH 20/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/python/dashboard/test_dashboard.py | 5 ++++- tests/python/dashboard/util.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 316e9f9d4..5fa480269 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -11,6 +11,7 @@ TIMEOUT = 60 + @pytest.mark.skipif( importlib.util.find_spec("seleniumbase") is None, reason="seleniumbase is not available", @@ -80,7 +81,9 @@ def test_dashboard(): sb.click("#Run_route") sb.click("#run_simulation_button") - assert look_for_text(sb, "#xterm_component", "Simulation complete.", timeout=TIMEOUT), "'Simulation compelte.' not found." + assert look_for_text( + sb, "#xterm_component", "Simulation complete.", timeout=TIMEOUT + ), "'Simulation compelte.' not found." finally: if app_process is not None: diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index 907ad6c81..fe08486de 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -1,9 +1,14 @@ import os import subprocess import sys -from selenium.common.exceptions import ElementNotInteractableException, JavascriptException import time +from selenium.common.exceptions import ( + ElementNotInteractableException, + JavascriptException, +) + + def wait_for_ready(sb, element_name, timeout=10): """ Waits until the specified element is present in the DOM. @@ -48,7 +53,10 @@ def set_input_value(sb, element_id, value, timeout=60): break except (ElementNotInteractableException, JavascriptException): if time.time() > end_time: - raise Exception(f"Element {selector} not interactable after {timeout} seconds.") + raise Exception( + f"Element {selector} not interactable after {timeout} seconds." + ) + def look_for_text(sb, element_id, text_to_look_for, timeout): """ @@ -66,6 +74,7 @@ def look_for_text(sb, element_id, text_to_look_for, timeout): return False + def start_dashboard(): """ Function which starts up impactx-dashboard server. From fcd0138d14d92b81d9e92d6f89c6d15f53743525 Mon Sep 17 00:00:00 2001 From: Parthib Roy Date: Thu, 19 Sep 2024 10:16:54 -0700 Subject: [PATCH 21/21] Redid logic to show simulation is complete --- .../impactx/dashboard/Analyze/plotsMain.py | 2 ++ .../impactx/dashboard/Toolbar/toolbarMain.py | 12 ++++++++++++ tests/python/dashboard/test_dashboard.py | 6 ++---- tests/python/dashboard/util.py | 18 +++++++----------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/python/impactx/dashboard/Analyze/plotsMain.py b/src/python/impactx/dashboard/Analyze/plotsMain.py index 6df7297fa..e85d49068 100644 --- a/src/python/impactx/dashboard/Analyze/plotsMain.py +++ b/src/python/impactx/dashboard/Analyze/plotsMain.py @@ -152,6 +152,7 @@ async def print_lines(): ctrl.terminal_print(line) ctrl.terminal_print("Simulation complete.") + asyncio.create_task(print_lines()) @@ -175,6 +176,7 @@ def on_filtered_data_change(**kwargs): @ctrl.add("run_simulation") def run_simulation_and_store(): + state.simulation_complete = True, state.plot_options = available_plot_options(simulationClicked=True) run_simulation_impactX() update_plot() diff --git a/src/python/impactx/dashboard/Toolbar/toolbarMain.py b/src/python/impactx/dashboard/Toolbar/toolbarMain.py index 63a10d6de..8295047c1 100644 --- a/src/python/impactx/dashboard/Toolbar/toolbarMain.py +++ b/src/python/impactx/dashboard/Toolbar/toolbarMain.py @@ -46,6 +46,17 @@ def run_simulation_button(): disabled=("disableRunSimulationButton", True), ) + @staticmethod + def show_simulation_complete(): + vuetify.VAlert( + "Simulation Complete", + v_model=("simulation_complete", False), + id="simulation_complete", + type="success", + dense=True, + classes="mt-4", + ) + # ----------------------------------------------------------------------------- # Content @@ -64,6 +75,7 @@ def run_toolbar(): """ vuetify.VSpacer(), + ToolbarElements.show_simulation_complete(), ToolbarElements.run_simulation_button(), @staticmethod diff --git a/tests/python/dashboard/test_dashboard.py b/tests/python/dashboard/test_dashboard.py index 5fa480269..a455d88d9 100644 --- a/tests/python/dashboard/test_dashboard.py +++ b/tests/python/dashboard/test_dashboard.py @@ -2,7 +2,7 @@ import pytest from util import ( - look_for_text, + check_until_visible, set_input_value, start_dashboard, wait_for_dashboard_ready, @@ -81,9 +81,7 @@ def test_dashboard(): sb.click("#Run_route") sb.click("#run_simulation_button") - assert look_for_text( - sb, "#xterm_component", "Simulation complete.", timeout=TIMEOUT - ), "'Simulation compelte.' not found." + assert check_until_visible(sb, "#simulation_complete"), "Simulation did not complete successfully." finally: if app_process is not None: diff --git a/tests/python/dashboard/util.py b/tests/python/dashboard/util.py index fe08486de..4b656fe50 100644 --- a/tests/python/dashboard/util.py +++ b/tests/python/dashboard/util.py @@ -58,20 +58,16 @@ def set_input_value(sb, element_id, value, timeout=60): ) -def look_for_text(sb, element_id, text_to_look_for, timeout): +def check_until_visible(sb, selector, timeout=10, interval=1): """ - Function which repeatedly checks for specific text every second until it appears or the timeout is reached. + Function which retries checking if an element is visible. """ - start_time = time.time() - while time.time() - start_time < timeout: - try: - if sb.is_text_visible(text_to_look_for, element_id): - return True - except Exception as e: - print(f"Error checking for text: {e}") - time.sleep(1) - + end_time = time.time() + timeout + while time.time() < end_time: + if sb.is_element_visible(selector): + return True + time.sleep(interval) return False