From 13c491301567a1698feb3446436bb6a256e0ea50 Mon Sep 17 00:00:00 2001 From: Antoine Pouille Date: Thu, 17 Oct 2024 18:02:59 +0200 Subject: [PATCH] tests ++ --- dev/publish-packages.md | 13 +- gui/state/runtime_web_workers.ml | 8 +- gui/state/state_project.ml | 19 +- gui/ui/panel_preferences.ml | 10 +- gui/ui/panel_projects_controller.ml | 13 +- gui/ui/panel_tabs/tab_stories.ml | 2 +- tests/playwright/procedure.spec.ts | 317 ++++++++++++++++++++++------ tests/playwright/webapp_utils.ts | 42 +++- 8 files changed, 323 insertions(+), 101 deletions(-) diff --git a/dev/publish-packages.md b/dev/publish-packages.md index bd2ead664..48a62f224 100644 --- a/dev/publish-packages.md +++ b/dev/publish-packages.md @@ -56,14 +56,14 @@ Create a new release draft - Title: ``` -Kappa Software Suite version 4.1.3 +Kappa Software Suite version v4.1.3 ``` - Text: ``` v4.1.3 -Kappa Software Suite version 4.1.3 +Kappa Software Suite version v4.1.3 ``` Set last release as previous tag, then click _Generate release notes_ and edit results to keep what's relevant @@ -74,12 +74,17 @@ Add files for: Kappapp_for_linux.tar.gz Kappapp_for_mac_os_10.15.zip Kappapp_for_windows.zip +``` +taken from nightly builds: https://tools.kappalanguage.org/nightly-builds/ + +Source code from release tag https://github.com/Kappa-Dev/KappaTools/releases/tag/v4.1.3 should appear automatically + +``` Source code (zip) Source code (tar.gz) ``` -electron are from nightly builds: https://tools.kappalanguage.org/nightly-builds/ -Source code from release tag https://github.com/Kappa-Dev/KappaTools/releases/tag/v4.1.3 + ### Pip diff --git a/gui/state/runtime_web_workers.ml b/gui/state/runtime_web_workers.ml index 19d68a7d7..57f31294f 100644 --- a/gui/state/runtime_web_workers.ml +++ b/gui/state/runtime_web_workers.ml @@ -224,7 +224,9 @@ class runtime_kasim_as_web_worker () : Api.concrete_manager = without_kasim#terminate; kasim_worker##terminate - method is_computing = without_kasim#is_computing || self#sim_is_computing + method is_computing = + without_kasim#is_computing || self#sim_is_computing + || self#story_is_computing method project_parse ~patternSharing overwrites = let simulation_load (patternSharing : Pattern.sharing_level) @@ -236,6 +238,7 @@ class runtime_kasim_as_web_worker () : Api.concrete_manager = overwrites end +(* TODO: deprecate this? *) class runtime_kasim_embedded_in_main_thread () : Api.concrete_manager = let system_process : Kappa_facade.system_process = object @@ -262,7 +265,8 @@ class runtime_kasim_embedded_in_main_thread () : Api.concrete_manager = without_kasim#terminate; () (*TODO*) - method is_computing = true (*TODO*) + method is_computing = + without_kasim#is_computing || self#is_computing || self#story_is_computing method project_parse ~patternSharing overwrites = let simulation_load patternSharing parsing_compil overwrites = diff --git a/gui/state/state_project.ml b/gui/state/state_project.ml index ebc4bc1ef..b9b63e8db 100644 --- a/gui/state/state_project.ml +++ b/gui/state/state_project.ml @@ -210,13 +210,13 @@ let set_show_non_weakly_reversible_transitions update_parameters (fun param -> { param with show_non_weakly_reversible_transitions }) -let update_state me project_catalog default_parameters project_parameters = - me.project_manager#project_parse ~patternSharing:Pattern.Compatible_patterns - [] +let update_state project project_catalog default_parameters project_parameters = + project.project_manager#project_parse + ~patternSharing:Pattern.Compatible_patterns [] >>= fun (out : unit Api.result) -> set_state { - project_current = Some me; + project_current = Some project; project_catalog; default_parameters; project_parameters; @@ -224,8 +224,9 @@ let update_state me project_catalog default_parameters project_parameters = }; Lwt.return out -let computing_watcher manager setter = +let computing_watcher (manager : Api.concrete_manager) (setter : bool -> unit) = let delay = 1. in + (* Note: cancel logic seems not to be implemented? *) let cancelled = ref false in let rec loop () = setter manager#is_computing; @@ -254,7 +255,7 @@ let add_project is_new project_id : unit Api.lwt_result = let project_watcher_cancel = computing_watcher project_manager (set_computes ?step:None) in - let me = + let project : a_project = { project_id; project_manager; @@ -267,9 +268,9 @@ let add_project is_new project_id : unit Api.lwt_result = Mods.StringMap.add project_id default_parameters state_va.project_parameters in - Lwt.return (Result_util.ok (me, me :: catalog, params)))) - >>= Api_common.result_bind_with_lwt ~ok:(fun (me, catalog, params) -> - update_state me catalog state_va.default_parameters params) + Lwt.return (Result_util.ok (project, project :: catalog, params)))) + >>= Api_common.result_bind_with_lwt ~ok:(fun (project, catalog, params) -> + update_state project catalog state_va.default_parameters params) let create_project project_id = add_project true project_id let set_project project_id = add_project false project_id diff --git a/gui/ui/panel_preferences.ml b/gui/ui/panel_preferences.ml index b3892fbd1..c0e4d9772 100644 --- a/gui/ui/panel_preferences.ml +++ b/gui/ui/panel_preferences.ml @@ -175,7 +175,11 @@ module InputPlotPeriod : Ui_common.Div = struct end module DivErrorMessage : Ui_common.Div = struct - let id = "configuration_error_div" + let id_error = "configuration_error_div" + let id_alert = "configuration_alert_div" + let id = id_error + (* TODO: clean this id matter *) + let message_nav_inc_id = "panel_preferences_message_nav_inc_id" let message_nav_dec_id = "panel_preferences_message_nav_dec_id" let message_file_label_id = "panel_preferences_message_file_label" @@ -276,14 +280,14 @@ module DivErrorMessage : Ui_common.Div = struct let error_message = Html.span - ~a:[ Html.a_id id; Html.a_class [ "error-span" ] ] + ~a:[ Html.a_id id_error; Html.a_class [ "error-span" ] ] [ Tyxml_js.R.Html.txt error_message_text ] let alert_messages = Html.div ~a: [ - Html.a_id id; + Html.a_id id_alert; Tyxml_js.R.Html.a_class (React.S.bind (Hooked.S.to_react_signal State_error.errors) (fun error -> diff --git a/gui/ui/panel_projects_controller.ml b/gui/ui/panel_projects_controller.ml index 10ab1cf42..078f0a84b 100644 --- a/gui/ui/panel_projects_controller.ml +++ b/gui/ui/panel_projects_controller.ml @@ -8,11 +8,14 @@ open Lwt.Infix -let refresh r = - let r' = State_file.sync ~reset:true () in - let r'' = State_simulation.refresh () in - r' >>= fun r' -> - r'' >>= fun r'' -> Lwt.return (Api_common.result_combine [ r; r'; r'' ]) +let refresh result_before = + let lwt_result_state_file_sync = State_file.sync ~reset:true () in + let lwt_result_simulation_refresh = State_simulation.refresh () in + lwt_result_state_file_sync >>= fun result_state_file_sync -> + lwt_result_simulation_refresh >>= fun result_simulation_refresh -> + Lwt.return + (Api_common.result_combine + [ result_before; result_state_file_sync; result_simulation_refresh ]) let create_project (project_id : string) : unit = Common.async __LOC__ (fun () -> diff --git a/gui/ui/panel_tabs/tab_stories.ml b/gui/ui/panel_tabs/tab_stories.ml index 16b38959a..153cf0b64 100644 --- a/gui/ui/panel_tabs/tab_stories.ml +++ b/gui/ui/panel_tabs/tab_stories.ml @@ -191,7 +191,7 @@ let rec inspect_stories () = >>= Result_util.fold ~ok:(fun is_computing -> if is_computing && React.S.value tab_is_active then - Js_of_ocaml_lwt.Lwt_js.sleep 3. >>= inspect_stories + Js_of_ocaml_lwt.Lwt_js.sleep 1. >>= inspect_stories else Lwt.return_unit) ~error:(fun _ -> Lwt.return_unit) diff --git a/tests/playwright/procedure.spec.ts b/tests/playwright/procedure.spec.ts index acf20d4ec..96ea6c6f4 100644 --- a/tests/playwright/procedure.spec.ts +++ b/tests/playwright/procedure.spec.ts @@ -1,6 +1,8 @@ // Kappapp tests // Note: trace snapshots that should be taken by playwright are not (absent on right of ui `npx playwright test --ui`) +// TODO: test with embedded Kasim? deprecate it? + import { test, expect, type Page } from '@playwright/test'; import * as utils from './webapp_utils'; @@ -39,8 +41,7 @@ test.describe('Editor tab', () => { await editor.press('Backspace'); // (useless comment to match brackets { {) await utils.expect_error(page, [ - " « 1/1 » [abc.ka] invalid internal state or missing '}' ", - " invalid internal state or missing '}' ", + " « 1/1 » [abc.ka] invalid internal state or missing '}' " ]); await editor_cancel(); await utils.expect_no_error(page); @@ -48,8 +49,7 @@ test.describe('Editor tab', () => { await editor.fill('\n%agent: D(a{u p})'); await utils.expect_error(page, [ - " « 1/1 » [abc.ka] Dead agent D ", - " Dead agent D ", + " « 1/1 » [abc.ka] Dead agent D " ]); await editor_to_line(25); @@ -60,8 +60,7 @@ test.describe('Editor tab', () => { await editor.fill("\n'd' D(a{p}) -> D(a{u}) @ 1"); // await page.locator('#panel_preferences_message_nav_inc_id').click(); await utils.expect_error(page, [ - " « 1/1 » [abc.ka] Dead rule 'd' ", - " Dead rule 'd' ", + " « 1/1 » [abc.ka] Dead rule 'd' " ]); await editor_cancel(); await editor_cancel(); @@ -95,7 +94,7 @@ test.describe('Editor tab', () => { test('influences', async ({ page }) => { const opts_screen = { threshold: 0.4 } await utils.open_app_with_model(page, abc_ka); - await page.getByRole('tab', { name: 'influences' }).click(); + await page.locator('#navinfluences').click(); await expect.soft(page.locator('#influences-table')).toHaveScreenshot(); await expect.soft(page.getByRole('cell', { name: 'Navigate through the nodes' })).toBeVisible(); await page.getByRole('button', { name: 'First node' }).click(); @@ -130,7 +129,7 @@ test.describe('Editor tab', () => { test('constraints_and_polymers_1', async ({ page }) => { await utils.open_app_with_model(page, abc_ka); - await page.getByRole('tab', { name: 'constraints' }).click(); + await page.locator('#navconstraints').click(); await expect.soft(constraint_locator(page, 0)).toHaveText( `A(c) => [ A(c[.]) v A(c[x1.C]) v A(c[x2.C]) ] A(x) => [ A(x[.]) v A(x[x.B]) ] @@ -145,7 +144,7 @@ C(x2) => [ C(x2{u}) v C(x2{p}) ] "C() => [ C(x1{p}[.],x2{u}[.]) v C(x1{p}[.],x2{u}[c.A]) v C(x1{p}[.],x2{p}[.]) v C(x1{u}[c.A],x2{u}[.]) v C(x1{u}[.],x2{u}[.]) ]" ); - await page.getByRole('tab', { name: 'polymers' }).click(); + await page.locator('#navpolymers').click(); await expect.soft(page.getByRole('paragraph')).toHaveText( "The size of biomolecular compounds is uniformly bounded." ); @@ -153,7 +152,7 @@ C(x2) => [ C(x2{u}) v C(x2{p}) ] test('constraints_and_polymers_2', async ({ page }) => { await utils.open_app_with_model(page, poly_ka); - await page.getByRole('tab', { name: 'polymers' }).click(); + await page.locator('#navpolymers').click(); await expect.soft(page.getByRole('paragraph')).toHaveText( `The following bonds may form arbitrary long chains of agents: @@ -170,7 +169,7 @@ A(c[1]),C(a[1]) test('constraints_and_polymers_3', async ({ page }) => { await utils.open_app_with_model(page, local_views_slide_69_ka, true); - await page.getByRole('tab', { name: 'constraints' }).click(); + await page.locator('#navconstraints').click(); await expect.soft(constraint_locator(page, 0)).toHaveText( `E(x) => [ E(x[.]) v E(x[x.R]) ] R(C) => [ R(C[.]) v R(C[CN.R]) ] @@ -200,14 +199,13 @@ R(CN[C.R],CR[CR.R]) => R(CN[2],CR[1]),R(C[2],CR[1]) "" ); await utils.expect_error(page, [ - " « 1/1 » [model.ka] Dead rule R(CR[1] C[2]), R(CN[2]), R(CR[1]) -> R(CR[1] C[2]), R(CN[2]), R(CR[1]) ", - " Dead rule R(CR[1] C[2]), R(CN[2]), R(CR[1]) -> R(CR[1] C[2]), R(CN[2]), R(CR[1]) " + " « 1/1 » [model.ka] Dead rule R(CR[1] C[2]), R(CN[2]), R(CR[1]) -> R(CR[1] C[2]), R(CN[2]), R(CR[1]) " ]); }); test('constraints_and_polymers_4', async ({ page }) => { await utils.open_app_with_model(page, counter_2_ka); - await page.getByRole('tab', { name: 'constraints' }).click(); + await page.locator('#navconstraints').click(); await expect.soft(constraint_locator(page, 4)).toHaveText( `A() => A(c{[0 .. 2]}) ` @@ -233,7 +231,7 @@ test.describe('Simulation tools', () => { await utils.set_pause_if(page, '[T] > 30'); await page.getByRole('button', { name: 'start' }).click(); await utils.wait_for_sim_stop(page, { timeout: 20000 }); - await page.getByRole('tab', { name: 'plot New' }).click(); + await page.locator('#navplot').click(); await expect.soft(page.getByRole('img')).toHaveScreenshot(); await utils.set_pause_if(page, '[T] > 100'); await page.getByRole('button', { name: 'continue' }).click(); @@ -294,7 +292,7 @@ test.describe('Simulation tools', () => { // Run simulation to 30, then 100, then test plot options await utils.set_pause_if(page, '[T] > 30'); await page.getByRole('button', { name: 'start' }).click(); - await page.getByRole('tab', { name: 'DIN' }).click(); + await page.locator('#navDIN').click(); await expectScreenShotDINTable(); await utils.testExports(page, '#export_din-export', 'flux', ['json', 'dot', 'html']); @@ -319,27 +317,25 @@ test.describe('Simulation tools', () => { await utils.set_pause_if(page, '[T] > 30'); await page.getByRole('button', { name: 'start' }).click(); await utils.apply_perturbation(page, ''); - await expect.soft(utils.get_error_field(page)).toHaveText("TODO"); + await expect.soft(utils.get_error_field(page)).toHaveText(" « 1/1 » [] Problematic effect list "); await utils.apply_perturbation(page, '$SNAPSHOT'); await utils.set_pause_if(page, '[T] > 60'); await page.getByRole('button', { name: 'continue' }).click(); await utils.apply_perturbation(page, '$SNAPSHOT "T60"'); // check log page - await page.getByRole('tab', { name: 'log New' }).click(); + await page.locator('#navlog').click(); await expect.soft(page.locator('#log div')).toHaveText( - `Building initial simulation conditions... -variable declarations -rules -interventions -observables -update_domain construction 21 (sub)observables 37 navigation steps -initial conditionsUser-set seed used for simulation: 1%mod: [E] = 14599 do $SNAPSHOT ;%mod: [E] = 28929 do $SNAPSHOT \"T60\"; -` + `+ Building initial simulation conditions... -variable declarations -rules -interventions -observables -update_domain construction 21 (sub)observables 37 navigation steps -initial conditionsUser-set seed used for simulation: 1%mod: [E] = 14599 do $SNAPSHOT ;%mod: [E] = 28929 do $SNAPSHOT \"T60\";` ); // go to tab snapshots and test display - await page.getByRole('tab', { name: 'snapshot' }).click(); + await page.locator('#navsnapshot').click(); const snapshot_display_loc = page.locator('.navcontent-view > .panel-scroll').first(); //const snapshot_map_display_loc = page.locator('#snapshot-map-display #map-container').first(); //const snapshot_map_display2_loc = page.locator('#snapshot-map-display div').first(); - await expect.soft(snapshot_display_loc).toHaveText( - `// Snapshot [Event: 14599] + const snapshot0 = `// Snapshot [Event: 14599] %def: "T0" "30.003207872859807" %init: 60 /*3 agents*/ A(x[1] c[2]), B(x[1]), C(x1{p}[.] x2{u}[2]) @@ -354,11 +350,30 @@ test.describe('Simulation tools', () => { %init: 474 /*1 agents*/ A(x[.] c[.]) -`); +`; + + const snapshot1 = `// Snapshot [Event: 28929] +%def: "T0" "60.00175023164761" + +%init: 60 /*3 agents*/ B(x[1]), A(x[1] c[2]), C(x1{p}[.] x2{u}[2]) +%init: 75 /*3 agents*/ A(x[1] c[2]), B(x[1]), C(x1{u}[2] x2{u}[.]) +%init: 3 /*2 agents*/ A(x[.] c[1]), C(x1{u}[1] x2{u}[.]) +%init: 94 /*2 agents*/ A(x[.] c[1]), C(x1{p}[.] x2{u}[1]) +%init: 2925 /*1 agents*/ C(x1{u}[.] x2{u}[.]) +%init: 5022 /*1 agents*/ C(x1{p}[.] x2{p}[.]) +%init: 1821 /*1 agents*/ C(x1{p}[.] x2{u}[.]) +%init: 258 /*2 agents*/ B(x[1]), A(x[1] c[.]) +%init: 607 /*1 agents*/ B(x[.]) +%init: 510 /*1 agents*/ A(x[.] c[.]) + + +`; + + await expect.soft(snapshot_display_loc).toHaveText(snapshot0); await page.locator('#snapshot-select-id').selectOption('1'); - await expect.soft(snapshot_display_loc).toHaveText("TODO"); + await expect.soft(snapshot_display_loc).toHaveText(snapshot1); await page.locator('#snapshot-select-id').selectOption('0'); - await expect.soft(snapshot_display_loc).toHaveText("TODO"); + await expect.soft(snapshot_display_loc).toHaveText(snapshot0); await page.locator('#format_select_id').selectOption('Graph'); await expect.soft(snapshot_display_loc).toHaveScreenshot(); await page.locator('.navcontent-view > div:nth-child(3)').click(); @@ -392,26 +407,30 @@ test.describe('Simulation tools', () => { // Generate two snapshots await utils.set_pause_if(page, '[T] > 30'); await page.getByRole('button', { name: 'start' }).click(); - await utils.apply_perturbation(page, '$PRINT "time: ".[T] > "time.txt"'); - await utils.apply_perturbation(page, '$PRINT \'AB\' > "ab.txt"'); + const print_time = '$PRINT "time: ".[T] > "time.txt"'; + const print_ab = '$PRINT \'AB\' > "ab.txt"'; + await utils.apply_perturbation(page, print_time); + await utils.apply_perturbation(page, print_ab); await utils.set_pause_if(page, '[T] > 60'); await page.getByRole('button', { name: 'continue' }).click(); - await utils.apply_perturbation(page, '$PRINT "time: ".[T] > "time.txt"'); - await utils.apply_perturbation(page, '$PRINT \'AB\' > "ab.txt"'); + await utils.apply_perturbation(page, print_time); + await utils.apply_perturbation(page, print_ab); // check log page - // await page.getByRole('tab', { name: 'log New' }).click(); - await page.locator('#navtabs').nth(1).click(); + await page.locator('#navlog').click(); await expect.soft(page.locator('#log div')).toHaveText( ` + Building initial simulation conditions... -variable declarations -rules -interventions -observables -update_domain construction 21 (sub)observables 37 navigation steps -initial conditionsUser-set seed used for simulation: 1%mod: [E] = 14599 do $PRINTF ("time: ".[T]) > "time.txt";%mod: [E] = 14599 do $PRINTF (AB) > "ab.txt";%mod: [E] = 28929 do $PRINTF ("time: ".[T]) > "time.txt";%mod: [E] = 28929 do $PRINTF (AB) > "ab.txt";` ); - await page.locator('#navtabs').nth(5).click(); + + // outputs page + await page.locator('#navoutputs').click(); + const outputs_display = page.locator('.show > .navcontent-view > .panel-scroll'); await page.locator('#output-select-id').selectOption('time.txt'); - await expect.soft(page.getByRole('paragraph')).toHaveText("TODO"); + await expect.soft(outputs_display).toHaveText("time: 30.0032078729time: 60.0017502316"); await page.locator('.list-group-item').click(); await page.locator('#output-select-id').selectOption('ab.txt'); - await expect.soft(page.getByRole('paragraph')).toHaveText("TODO"); + await expect.soft(outputs_display).toHaveText("394393"); const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'All outputs' }).click(); @@ -424,7 +443,7 @@ test.describe('Simulation tools', () => { test.describe('stories', () => { - test('stories', async ({ page }) => { + async function setup_stories(page: Page) { await utils.open_app_with_model(page, causality_slide_10_ka, true); await utils.setSeed(page, 1); @@ -436,50 +455,211 @@ test.describe('stories', () => { await page.getByRole('button', { name: 'start' }).click(); await utils.wait_for_sim_stop(page, { timeout: 20000 }); - async function expect_texts_story_logs(page: Page, text_info_log: string, text_computation_log: string) { - await page.getByRole('tab', { name: 'story_info_log' }).click(); - await expect.soft(page.locator('#story_info_log')).toHaveText(text_info_log); - await page.getByRole('tab', { name: 'stories_computation_log' }).click(); - await expect.soft(page.locator('#story_computation_log')).toHaveText(text_computation_log); - await page.getByRole('tab', { name: 'story_info_log' }).click(); - } + // go to stories tab + await page.locator('#navstories').click(); + // uncheck weakly + await page.getByRole('checkbox', { name: 'Weakly' }).uncheck(); + } - async function computeStoriesAndWait(page: Page) { - await page.getByRole('button', { name: 'Launch' }).click(); - // wait for icon in project to be checkmark - await utils.wait_for_project_ready_status(page, { timeout: 30000 }); - } + async function expect_texts_story_logs(page: Page, text_info_log: string, text_computation_log: string) { + await page.getByRole('tab', { name: 'story_info_log' }).click(); + await expect.soft(page.locator('#story_info_log')).toHaveText(text_info_log); + await page.getByRole('tab', { name: 'stories_computation_log' }).click(); + await expect.soft(page.locator('#stories_computation_log')).toHaveText(text_computation_log); + await page.getByRole('tab', { name: 'story_info_log' }).click(); + } + + async function computeStoriesAndWait(page: Page) { + await page.getByRole('button', { name: 'Launch' }).click(); + // wait for icon in project to be busy `refresh`, the be back to checkmark + // (the `post` to the worker takes some time, which during when the icon is not refreshed) + // TODO: update this when icon refresh is fixed + await utils.wait_for_project_ready_status(page, { timeout: 30000 }, true); + } - async function computeStoriesAndTest(page: Page, text_info_log: string, text_computation_log: string) { - await computeStoriesAndWait(page); - await expect_texts_story_logs(page, text_info_log, text_computation_log); + async function computeStoriesAndTest(page: Page, text_info_log: string, text_computation_log: string, expect_screenshot: boolean = true) { + await computeStoriesAndWait(page); + await expect_texts_story_logs(page, text_info_log, text_computation_log); + if (expect_screenshot) { await expect.soft(page.getByRole('img')).toHaveScreenshot(); } + } - // compare stories - await page.getByRole('tab', { name: 'stories' }).click(); - await page.getByRole('checkbox', { name: 'Weakly' }).uncheck(); - await computeStoriesAndTest(page, "TODO", "TODO"); + test('Empty', async ({ page }) => { + await setup_stories(page); + // No screenshot test as no stories causes no image to locate + await computeStoriesAndTest(page, "", `Starting Compression +Compression completed +0 stories +`, false); + }); + + test('Weakly', async ({ page }) => { + await setup_stories(page); + await page.getByRole('checkbox', { name: 'Weakly' }).check(); + await computeStoriesAndTest(page, + `ids: 11, 19, 24, 29, 33, 36, 37, 39, 49, 52, 55 +t=0.0975547254717, 0.153578551271, 0.171807609491, 0.195251616607, + 0.211330564253, 0.218683483843, 0.219103111568, 0.231032193424, + 0.275917880116, 0.287969294047, 0.299259752742 +event=2825, 3877, 4260, 4765, 5067, 5218, 5225, 5477, 6336, 6579, 6766`, + `Starting Compression +Start one weak compression +Start one weak compression +Start one weak compression +Compression completed +3 stories +`); + }); + test('Strongly', async ({ page }) => { + await setup_stories(page); await page.getByRole('checkbox', { name: 'Strongly' }).check(); - await page.getByRole('checkbox', { name: 'Strongly' }).uncheck(); - await computeStoriesAndTest(page, "TODO", "TODO"); - await page.getByRole('checkbox', { name: 'Causal' }).check(); - await page.getByRole('checkbox', { name: 'Causal' }).uncheck(); - await computeStoriesAndTest(page, "TODO", "TODO"); + await computeStoriesAndTest(page, + `ids: 11, 19, 24, 29, 33, 36, 37, 39, 49, 52, 55, 5, 8, 21, 27, 28, 30, 31, + 32, 41, 42, 46, 47, 50, 51, 56, 1, 2, 3, 4, 6, 7, 9, 10, 12, 13, 14, 15, + 16, 17, 18, 20, 22, 23, 25, 26, 34, 35, 38, 40, 43, 44, 45, 48, 53, 54 +t=0.0975547254717, 0.153578551271, 0.171807609491, 0.195251616607, + 0.211330564253, 0.218683483843, 0.219103111568, 0.231032193424, + 0.275917880116, 0.287969294047, 0.299259752742, 0.0500738649842, + 0.0884797831425, 0.161217577071, 0.18332499113, 0.186583686508, + 0.195789013044, 0.198978506925, 0.205627414085, 0.250574592161, + 0.26335509208, 0.272985766182, 0.274937277942, 0.28313183861, + 0.28605881908, 0.299316030636, 0.0137256652424, 0.0424530222206, + 0.0449356024089, 0.0459328458692, 0.0657465273502, 0.0702375030214, + 0.0958494110054, 0.0975448431689, 0.101919958829, 0.1115605239, + 0.117581700446, 0.125390166878, 0.126609656237, 0.139072907093, + 0.150316974123, 0.154324059758, 0.163465828786, 0.167329986128, + 0.172870749643, 0.181525571054, 0.21396617172, 0.217633967976, + 0.228163976202, 0.236188125158, 0.265437118532, 0.267501895402, + 0.272427852987, 0.274945449678, 0.28820790984, 0.288299009288 +event=2825, 3877, 4260, 4765, 5067, 5218, 5225, 5477, 6336, 6579, 6766, 1815, + 2638, 4029, 4505, 4581, 4775, 4822, 4950, 5834, 6102, 6272, 6316, 6491, + 6544, 6767, 1103, 1662, 1709, 1728, 2159, 2246, 2781, 2823, 2900, 3076, + 3184, 3329, 3347, 3577, 3793, 3890, 4083, 4170, 4282, 4459, 5118, 5200, + 5423, 5563, 6127, 6167, 6263, 6317, 6581, 6583`, + `Starting Compression +Start one strong compression +Start one strong compression +Start one strong compression +Compression completed +1 stories +`); + + }); + test('Causal + select stories', async ({ page }) => { + await setup_stories(page); + await page.getByRole('checkbox', { name: 'Causal' }).check(); + const computation_log = `Starting Compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Start one causal compression +Compression completed +26 stories +`; + + await computeStoriesAndTest(page, + `ids: 56 t=0.299316030636 event=6767`, + computation_log); await page.getByRole('combobox').selectOption('0'); await expect.soft(page.getByRole('img')).toHaveScreenshot(); - await expect_texts_story_logs(page, "TODO", "TODO"); + await expect_texts_story_logs(page, + `ids: 43, 26, 22, 20, 16, 15, 10, 9, 6, 4, 3, 2, 1 +t=0.265437118532, 0.181525571054, 0.163465828786, 0.154324059758, + 0.126609656237, 0.125390166878, 0.0975448431689, 0.0958494110054, + 0.0657465273502, 0.0459328458692, 0.0449356024089, 0.0424530222206, + 0.0137256652424 +event=6127, 4459, 4083, 3890, 3347, 3329, 2823, 2781, 2159, 1728, 1709, 1662, + 1103`, + computation_log); await page.getByRole('combobox').selectOption('2'); await expect.soft(page.getByRole('img')).toHaveScreenshot(); - await expect_texts_story_logs(page, "TODO", "TODO"); + await expect_texts_story_logs(page, + `ids: 35, 14, 7 t=0.217633967976, 0.117581700446, 0.0702375030214 +event=5200, 3184, 2246`, + computation_log); + }); + test('Weakly + Strongly', async ({ page }) => { + await setup_stories(page); await page.getByRole('checkbox', { name: 'Weakly' }).check(); await page.getByRole('checkbox', { name: 'Strongly' }).check(); - await computeStoriesAndTest(page, "TODO", "TODO"); + await computeStoriesAndTest(page, + `ids: 11, 19, 24, 29, 33, 36, 37, 39, 49, 52, 55 +t=0.0975547254717, 0.153578551271, 0.171807609491, 0.195251616607, + 0.211330564253, 0.218683483843, 0.219103111568, 0.231032193424, + 0.275917880116, 0.287969294047, 0.299259752742 +event=2825, 3877, 4260, 4765, 5067, 5218, 5225, 5477, 6336, 6579, 6766` + , + `Starting Compression +Start one weak compression +Start one weak compression +Start one weak compression +Start one strong compression +Start one strong compression +Start one strong compression +Compression completed +3 stories +`); + }); + test('Trace download', async ({ page }) => { + await setup_stories(page); const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'get trace' }).click(); const download = await downloadPromise; @@ -494,9 +674,10 @@ test.describe('projects_and_files', () => { await utils.setSeed(page, 1); // open new project + const project_name = 'new_project' await page.getByRole('list').locator('a').nth(1).click(); await page.getByPlaceholder('project new').click(); - await page.getByPlaceholder('project new').fill('test'); + await page.getByPlaceholder('project new').fill(project_name); await page.getByPlaceholder('project new').press('Enter'); // add new file "abc.ka" and fill it await page.getByRole('button', { name: 'File' }).click(); @@ -511,8 +692,10 @@ test.describe('projects_and_files', () => { const contact_map = page.locator('#map-container'); await expect.soft(contact_map).toHaveScreenshot(); await page.getByRole('list').locator('a').nth(1).click(); + await page.waitForTimeout(2000); await expect.soft(contact_map).toHaveScreenshot(); await page.getByRole('list').locator('a').first().click(); + await page.waitForTimeout(2000); await expect.soft(contact_map).toHaveScreenshot(); // TODO: Could also check simulation results @@ -534,8 +717,10 @@ test.describe('projects_and_files', () => { await page.getByRole('button', { name: 'File' }).click(); await page.locator('#menu-editor-file-close-li').click(); await page.getByRole('button', { name: 'File' }).click(); + const fileChooserPromise = page.waitForEvent('filechooser'); await page.locator('#menu-editor-file-open-li').click(); - await page.locator('body').setInputFiles(downloaded_path); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(downloaded_path); // write other file and check contact map await page.getByRole('button', { name: 'File' }).click(); @@ -564,7 +749,7 @@ test.describe('projects_and_files', () => { await utils.set_pause_if(page, '[T] > 30'); await page.getByRole('button', { name: 'start' }).click(); await utils.wait_for_sim_stop(page, { timeout: 20000 }); - await page.getByRole('tab', { name: 'plot New' }).click(); + await page.locator('#navplot').click(); await expect.soft(page.getByRole('img')).toHaveScreenshot(); }); diff --git a/tests/playwright/webapp_utils.ts b/tests/playwright/webapp_utils.ts index e051ed7db..2565c2e00 100644 --- a/tests/playwright/webapp_utils.ts +++ b/tests/playwright/webapp_utils.ts @@ -22,22 +22,42 @@ function timeout_of_options(options?: { timeout?: number | undefined; visible?: return timeout; } +// TODO: test for contains and not == + // Used as playwright does not seemed to offer a way to have this logic -async function expect_locator_toHaveInnerHtml(page: Page, locator: Locator, value: any, timeout: number) { - const end_time = Date.now() + timeout; - var is_equal = false; +async function expect_locator_toHaveInnerHtml(page: Page, locator: Locator, value: any, timeout: number, retry_timeout: number = 500, allow_include: boolean = false) { + const end_time: number = Date.now() + timeout; + var is_equal: boolean = false; while (Date.now() < end_time && !(is_equal)) { const html = (await locator.innerHTML()); - is_equal = (html == value); - await page.waitForTimeout(1000); + if (allow_include) { + is_equal = (html.includes(value)); + } else { + is_equal = (html == value); + }; + await page.waitForTimeout(retry_timeout); } expect(is_equal).toBeTruthy(); } -export async function wait_for_project_ready_status(page: Page, options?: { timeout?: number | undefined; visible?: boolean | undefined; } | undefined) { - // wait for icon in project to be checkmark +export async function wait_for_project_ready_status(page: Page, options?: { timeout?: number | undefined; visible?: boolean | undefined; } | undefined, check_busy: boolean = false, project_name: string | undefined) { const timeout = timeout_of_options(options); - await expect_locator_toHaveInnerHtml(page, page.getByRole('list').locator('a').first(), " default", timeout); + const locator_first_tab = page.getByRole('list').locator('a').first(); + + if (check_busy) { + // wait for the icon to go to busy state before waiting to be back to ready state + // [retry_timeout] is low to try not to miss if the change to "refresh" is fast + // (useful for stories computation that at the moment don't change the icon state rightaway) + await expect_locator_toHaveInnerHtml(page, locator_first_tab, "\"glyphicon glyphicon-refresh\"", timeout, 50, true); + } + + // wait for project change if provided + if (project_name !== undefined) { + await expect_locator_toHaveInnerHtml(page, locator_first_tab, project_name, timeout, 500, true); + } + + // wait for icon in project to be checkmark + await expect_locator_toHaveInnerHtml(page, locator_first_tab, "\"glyphicon glyphicon-ok\"", timeout, 500, true); } export async function wait_for_sim_stop(page: Page, options?: { timeout?: number | undefined; visible?: boolean | undefined; } | undefined) { @@ -85,7 +105,8 @@ export async function open_app_with_model(page: Page, url_protocol_relative: str } export function get_error_field(page: Page) { - return page.locator('#configuration_error_div'); + // return page.locator('#configuration_error_div'); + return page.locator('#configuration_alert_div'); } export async function expect_error(page: Page, text: string[]): Promise { @@ -94,8 +115,7 @@ export async function expect_error(page: Page, text: string[]): Promise { export async function expect_no_error(page: Page): Promise { await expect_error(page, [ - " « » ", - "", + " « » " ]); }