From 8c0a01b0ed502a94d572842b4771a45f3be98a5f Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 08:57:15 +0300 Subject: [PATCH 01/25] Improved method to check JS errors --- pom.xml | 2 +- src/main/java/in/virit/mopo/Mopo.java | 39 ++++++++++++++++++++ src/test/java/firitin/pw/AddonHelpersIT.java | 38 +++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 9c4e0fe..3132829 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ com.microsoft.playwright playwright - 1.44.0 + 1.53.0 diff --git a/src/main/java/in/virit/mopo/Mopo.java b/src/main/java/in/virit/mopo/Mopo.java index 107364f..be41878 100644 --- a/src/main/java/in/virit/mopo/Mopo.java +++ b/src/main/java/in/virit/mopo/Mopo.java @@ -1,6 +1,7 @@ package in.virit.mopo; import com.microsoft.playwright.Browser; +import com.microsoft.playwright.ConsoleMessage; import com.microsoft.playwright.ElementHandle; import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; @@ -26,6 +27,28 @@ public Mopo(Page page) { this.page = page; } + private List consoleErrors; + + public void trackConsoleErrors() { + consoleErrors = new ArrayList<>(); + page.onConsoleMessage(msg -> { + System.out.println("Console message: " + msg.type() + " " + msg.text()); + if (msg.type().equals("error")) { + consoleErrors.add(msg); + } + }); + } + + /** + * Returns the list of console errors that have been logged since the + * {@link #trackConsoleErrors()} was called. + * + * @return a list of console messages that are errors + */ + public List getConsoleErrors() { + return consoleErrors; + } + /** * Waits until the client-server communication by Vaadin has settled. * @@ -60,7 +83,10 @@ public static void waitForConnectionToSettle(Page page, int minWait) { * Asserts that there are no JS errors in the dev console. * * @param page the page to be checked + * + * @deprecated this method depends on dev mode, consider using {@link #failIfJsErrorsFound()} and {@link #trackConsoleErrors()} that use browser console. */ + @Deprecated public static void assertNoJsErrors(Page page) { try { @@ -90,7 +116,10 @@ public void waitForConnectionToSettle() { /** * Asserts that there are no JS errors in the dev console. + * + * @deprecated this method depends on dev mode, consider using {@link #failIfJsErrorsFound()} and {@link #trackConsoleErrors()} that use browser console. */ + @Deprecated public void assertNoJsErrors() { assertNoJsErrors(page); } @@ -180,4 +209,14 @@ public void click(String selector) { page.locator(selector).click(); waitForConnectionToSettle(); } + + public void failIfJsErrorsFound() { + if (consoleErrors != null && !consoleErrors.isEmpty()) { + StringBuilder sb = new StringBuilder("There are JS errors in the console:\n"); + for (ConsoleMessage msg : consoleErrors) { + sb.append(msg.type()).append(": ").append(msg.text()).append("\n"); + } + throw new AssertionError(sb.toString()); + } + } } diff --git a/src/test/java/firitin/pw/AddonHelpersIT.java b/src/test/java/firitin/pw/AddonHelpersIT.java index a0a823d..2a69bde 100644 --- a/src/test/java/firitin/pw/AddonHelpersIT.java +++ b/src/test/java/firitin/pw/AddonHelpersIT.java @@ -6,6 +6,7 @@ import com.microsoft.playwright.Playwright; import in.virit.mopo.Mopo; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -21,9 +22,6 @@ public class AddonHelpersIT { static Playwright playwright = Playwright.create(); - static { - } - private Browser browser; private Page page; private Mopo mopo; @@ -68,8 +66,40 @@ public void doRandomStuffAndChangeFirstRow() throws InterruptedException { assertThat(page.locator("vaadin-dev-tools>div.error")).isAttached(); } + @Test + public void checkJsErrorsViaConsole() throws InterruptedException { + mopo.trackConsoleErrors(); + + page.navigate("http://localhost:" + port + "/addonhelpers"); + + mopo.waitForConnectionToSettle(); + + Assertions.assertEquals(0, mopo.getConsoleErrors().size(), + "There should be no console errors after the page has loaded"); + + // a helper message to fail the test if there is a JS error + mopo.failIfJsErrorsFound(); + + // This old version checks same from the Vaadin Dev Tools + mopo.assertNoJsErrors(); + + page.getByText("Throw JS exception").click(); + + mopo.waitForConnectionToSettle(); + + Assertions.assertTrue(mopo.getConsoleErrors().size() > 0, + "There should be at least one JS error in the console"); - @Test + try { + mopo.failIfJsErrorsFound(); + } catch (java.lang.AssertionError e) { + // Expected + System.out.println("Expected JS error in console."); + } + + } + + @Test public void listView() { // One could now open each of these and e.g. check for not JS errors List developmentTimeViewNames = mopo.getViewsReportedByDevMode(browser, "http://localhost:" + port + "/"); From ceda0d5fe63983c98ef9f3bf29bfced3434b4265 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 14:34:04 +0300 Subject: [PATCH 02/25] Upgrades for compatibility with latest Vaadin version --- pom.xml | 59 +++++++++------ src/main/java/in/virit/mopo/ComboBoxPw.java | 7 ++ src/main/java/in/virit/mopo/GridPw.java | 5 +- src/main/java/in/virit/mopo/Mopo.java | 75 +++++++++++-------- src/test/java/firitin/pw/AddonHelpersIT.java | 33 ++++---- src/test/java/firitin/pw/ComboBoxIT.java | 2 + .../java/firitin/pw/GridPlaywrightIT.java | 5 ++ .../java/firitin/ui/AddOnHelpersView.java | 2 + 8 files changed, 114 insertions(+), 74 deletions(-) diff --git a/pom.xml b/pom.xml index 3132829..05a2685 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ Java Playwright helpers for Vaadin users - 24.3.5 + 24.8.4 17 17 UTF-8 @@ -42,17 +42,6 @@ repo - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - @@ -177,9 +166,12 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.1.1 + true + false release + deploy @@ -191,20 +183,19 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.6.0 true - ossrh - https://oss.sonatype.org/ - true + central + true org.codehaus.mojo flatten-maven-plugin - 1.2.5 + 1.7.0 oss @@ -225,10 +216,36 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.7.0 + + + attach-javadocs + + jar + + + + org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.7 sign-artifacts diff --git a/src/main/java/in/virit/mopo/ComboBoxPw.java b/src/main/java/in/virit/mopo/ComboBoxPw.java index a132d50..36d39c0 100644 --- a/src/main/java/in/virit/mopo/ComboBoxPw.java +++ b/src/main/java/in/virit/mopo/ComboBoxPw.java @@ -28,7 +28,14 @@ public ComboBoxPw(Locator gridLocator) { */ public void filterAndSelectFirst(String filter) { filter(filter); + + // seems to be needed with latest Vaadin versions + assertThat(root).not().hasAttribute("loading", ""); + root.locator("input").press("Enter"); + + // seems to be needed with latest Vaadin versions + assertThat(root).not().hasAttribute("opened", ""); } /** diff --git a/src/main/java/in/virit/mopo/GridPw.java b/src/main/java/in/virit/mopo/GridPw.java index b6f58e4..e5eed2b 100644 --- a/src/main/java/in/virit/mopo/GridPw.java +++ b/src/main/java/in/virit/mopo/GridPw.java @@ -179,9 +179,8 @@ private RowPw(int rowIndex) { */ public Locator getCell(int cellIndex) { int indexInVirtualTable = (Integer) root.evaluate("g => g._getRenderedRows().indexOf(g._getRenderedRows().filter(r => r.index == %s)[0]);".formatted(rowIndex)); - indexInVirtualTable += 1; // 1-based :-) - String name = root.locator("#items tr:nth-child(%s) td:nth-child(%s) slot".formatted(indexInVirtualTable, cellIndex + 1)) - .getAttribute("name"); + Locator row = root.locator("#items tr").filter(new Locator.FilterOptions().setVisible(true)).nth(indexInVirtualTable); + String name = row.locator("td:nth-child(%s) slot".formatted(cellIndex + 1)).getAttribute("name"); return root.locator("vaadin-grid-cell-content[slot='%s']".formatted(name)); } diff --git a/src/main/java/in/virit/mopo/Mopo.java b/src/main/java/in/virit/mopo/Mopo.java index be41878..c70d625 100644 --- a/src/main/java/in/virit/mopo/Mopo.java +++ b/src/main/java/in/virit/mopo/Mopo.java @@ -1,7 +1,6 @@ package in.virit.mopo; import com.microsoft.playwright.Browser; -import com.microsoft.playwright.ConsoleMessage; import com.microsoft.playwright.ElementHandle; import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; @@ -17,6 +16,7 @@ public class Mopo { private final Page page; + private List clientSideErrors = new ArrayList<>(); /** * Constructs a new Mopo for given page @@ -27,28 +27,6 @@ public Mopo(Page page) { this.page = page; } - private List consoleErrors; - - public void trackConsoleErrors() { - consoleErrors = new ArrayList<>(); - page.onConsoleMessage(msg -> { - System.out.println("Console message: " + msg.type() + " " + msg.text()); - if (msg.type().equals("error")) { - consoleErrors.add(msg); - } - }); - } - - /** - * Returns the list of console errors that have been logged since the - * {@link #trackConsoleErrors()} was called. - * - * @return a list of console messages that are errors - */ - public List getConsoleErrors() { - return consoleErrors; - } - /** * Waits until the client-server communication by Vaadin has settled. * @@ -63,7 +41,7 @@ public static void waitForConnectionToSettle(Page page) { /** * Waits until the client-server communication by Vaadin has settled. * - * @param page the page on which Vaadin app is expected to be run + * @param page the page on which Vaadin app is expected to be run * @param minWait the minimum wait time spent to watch if client-server communication starts */ public static void waitForConnectionToSettle(Page page, int minWait) { @@ -83,8 +61,7 @@ public static void waitForConnectionToSettle(Page page, int minWait) { * Asserts that there are no JS errors in the dev console. * * @param page the page to be checked - * - * @deprecated this method depends on dev mode, consider using {@link #failIfJsErrorsFound()} and {@link #trackConsoleErrors()} that use browser console. + * @deprecated this method depends on dev mode, consider using {@link #failOnClientSideErrors()} ()} and {@link #trackClientSideErrors()} that use browser console. */ @Deprecated public static void assertNoJsErrors(Page page) { @@ -106,6 +83,35 @@ public static void assertNoJsErrors(Page page) { } } + /** + * Starts monitoring browser console and errors and collects + * those for further inspection (e.g. with {@link #getClientSideErrors()} or {@link #failOnClientSideErrors()}). + */ + public void trackClientSideErrors() { + page.waitForTimeout(1000); + page.onConsoleMessage(msg -> { + System.out.println("Console message: " + msg.type() + " " + msg.text()); + if (msg.type().equals("error")) { + clientSideErrors.add(msg.text()); + } + }); + + page.onPageError(error -> { + System.out.println("Page error: " + error); + clientSideErrors.add(error); + }); + } + + /** + * Returns the list of console errors that have been logged since the + * {@link #trackClientSideErrors()} was called. + * + * @return a list of console messages that are errors + */ + public List getClientSideErrors() { + return clientSideErrors; + } + /** * Waits until the client-server communication by Vaadin * has settled. @@ -117,7 +123,7 @@ public void waitForConnectionToSettle() { /** * Asserts that there are no JS errors in the dev console. * - * @deprecated this method depends on dev mode, consider using {@link #failIfJsErrorsFound()} and {@link #trackConsoleErrors()} that use browser console. + * @deprecated this method depends on dev mode, consider using {@link #failOnClientSideErrors()} ()} and {@link #trackClientSideErrors()} that use browser console. */ @Deprecated public void assertNoJsErrors() { @@ -210,13 +216,18 @@ public void click(String selector) { waitForConnectionToSettle(); } - public void failIfJsErrorsFound() { - if (consoleErrors != null && !consoleErrors.isEmpty()) { + /** + * Asserts that there are no client-side errors in the console. + * Throws a RuntimeException if there are any errors. + */ + public void failOnClientSideErrors() { + List consoleErrors = getClientSideErrors(); + if (!consoleErrors.isEmpty()) { StringBuilder sb = new StringBuilder("There are JS errors in the console:\n"); - for (ConsoleMessage msg : consoleErrors) { - sb.append(msg.type()).append(": ").append(msg.text()).append("\n"); + for (var msg : consoleErrors) { + sb.append(msg).append("\n"); } - throw new AssertionError(sb.toString()); + throw new RuntimeException("JS errors discovered: "+ sb.toString()); } } } diff --git a/src/test/java/firitin/pw/AddonHelpersIT.java b/src/test/java/firitin/pw/AddonHelpersIT.java index 2a69bde..735531b 100644 --- a/src/test/java/firitin/pw/AddonHelpersIT.java +++ b/src/test/java/firitin/pw/AddonHelpersIT.java @@ -45,7 +45,7 @@ public void closePlaywright() { browser.close(); } - @Test + //@Test // Disabled because with latest Vaadin version copilot/devmode don't seem to load anymore and probably non-functional public void doRandomStuffAndChangeFirstRow() throws InterruptedException { page.navigate("http://localhost:" + port + "/addonhelpers"); @@ -68,35 +68,33 @@ public void doRandomStuffAndChangeFirstRow() throws InterruptedException { @Test public void checkJsErrorsViaConsole() throws InterruptedException { - mopo.trackConsoleErrors(); - + // Note, start tracking console errors only here, as Vaadin gives one favicon related error by default + mopo.trackClientSideErrors(); page.navigate("http://localhost:" + port + "/addonhelpers"); mopo.waitForConnectionToSettle(); - Assertions.assertEquals(0, mopo.getConsoleErrors().size(), + mopo.getClientSideErrors().clear(); // Vaadin gives one favicon related error by default, so clear it + + Assertions.assertEquals(0, mopo.getClientSideErrors().size(), "There should be no console errors after the page has loaded"); // a helper message to fail the test if there is a JS error - mopo.failIfJsErrorsFound(); - - // This old version checks same from the Vaadin Dev Tools - mopo.assertNoJsErrors(); - page.getByText("Throw JS exception").click(); - mopo.waitForConnectionToSettle(); + assertThat(page.getByText("Error should have been thrown!")).isVisible(); - Assertions.assertTrue(mopo.getConsoleErrors().size() > 0, - "There should be at least one JS error in the console"); + // You could call this in the beginning of the test, but testing it is nasty + // mopo.failOnClientSideErrorsOnClose(); + // This is pretty much the same as above but inverted for testing + boolean thrown = false; try { - mopo.failIfJsErrorsFound(); - } catch (java.lang.AssertionError e) { - // Expected - System.out.println("Expected JS error in console."); + mopo.failOnClientSideErrors(); + } catch (RuntimeException e) { + thrown = true; } - + Assertions.assertTrue(thrown, "There should be an exception thrown"); } @Test @@ -104,6 +102,5 @@ public void listView() { // One could now open each of these and e.g. check for not JS errors List developmentTimeViewNames = mopo.getViewsReportedByDevMode(browser, "http://localhost:" + port + "/"); developmentTimeViewNames.forEach(System.out::println); - } } diff --git a/src/test/java/firitin/pw/ComboBoxIT.java b/src/test/java/firitin/pw/ComboBoxIT.java index 51bb5b2..5eb7f05 100644 --- a/src/test/java/firitin/pw/ComboBoxIT.java +++ b/src/test/java/firitin/pw/ComboBoxIT.java @@ -59,6 +59,8 @@ public void rawUsage() throws InterruptedException { Locator cb = page.locator("input[role='combobox']"); cb.fill("foo"); + // seems to be needed with latest Vaadin versions + page.waitForTimeout(1000); cb.press("Enter"); assertThat(value).containsText("foo"); diff --git a/src/test/java/firitin/pw/GridPlaywrightIT.java b/src/test/java/firitin/pw/GridPlaywrightIT.java index 1205255..65c0484 100644 --- a/src/test/java/firitin/pw/GridPlaywrightIT.java +++ b/src/test/java/firitin/pw/GridPlaywrightIT.java @@ -99,6 +99,11 @@ public void doRandomStuffAndChangeFirstRow() throws InterruptedException { // Get the first cell of the first row in Grid and check text // TODO add API to get cell by column header text + page.waitForTimeout(1000); + page.waitForTimeout(1000); + + mopo.waitForConnectionToSettle(); + assertThat(grid.getRow(0).getCell(0)).hasText(newFirstName); assertThat(grid.getRow(0).getCell("First Name")).hasText(newFirstName); diff --git a/src/test/java/firitin/ui/AddOnHelpersView.java b/src/test/java/firitin/ui/AddOnHelpersView.java index 1a72f70..097944d 100644 --- a/src/test/java/firitin/ui/AddOnHelpersView.java +++ b/src/test/java/firitin/ui/AddOnHelpersView.java @@ -1,6 +1,7 @@ package firitin.ui; import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; @@ -13,6 +14,7 @@ public AddOnHelpersView() { add(new Button("Throw JS exception", e -> { // deliberately cause a JS exception getElement().executeJs("window.foo();"); + add(new Paragraph("Error should have been thrown!")); })); } } From b23438179d08a239dbc37d7b84829560f6ae56c6 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 14:37:47 +0300 Subject: [PATCH 03/25] [maven-release-plugin] prepare release mopo-0.0.4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 05a2685..28bd412 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.4-SNAPSHOT + 0.0.4 Mopo jar Java Playwright helpers for Vaadin users @@ -28,7 +28,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - HEAD + mopo-0.0.4 From 0f571b747910a614870bb6a2f59170d6829e3ee2 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 14:37:51 +0300 Subject: [PATCH 04/25] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 28bd412..a0a19e5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.4 + 0.0.5-SNAPSHOT Mopo jar Java Playwright helpers for Vaadin users @@ -28,7 +28,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - mopo-0.0.4 + HEAD From ddbb42a7e697b7f6cf2c9fe23fc4c0587e69871e Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 15:34:35 +0300 Subject: [PATCH 05/25] Removed a ton of obsolete logging --- src/main/java/in/virit/mopo/Mopo.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/in/virit/mopo/Mopo.java b/src/main/java/in/virit/mopo/Mopo.java index c70d625..10cc64b 100644 --- a/src/main/java/in/virit/mopo/Mopo.java +++ b/src/main/java/in/virit/mopo/Mopo.java @@ -90,14 +90,12 @@ public static void assertNoJsErrors(Page page) { public void trackClientSideErrors() { page.waitForTimeout(1000); page.onConsoleMessage(msg -> { - System.out.println("Console message: " + msg.type() + " " + msg.text()); if (msg.type().equals("error")) { clientSideErrors.add(msg.text()); } }); page.onPageError(error -> { - System.out.println("Page error: " + error); clientSideErrors.add(error); }); } From e6a88dd79945246950f0d9d1260433c3da14ee1c Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 15:34:46 +0300 Subject: [PATCH 06/25] [maven-release-plugin] prepare release mopo-0.0.5 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a0a19e5..ba75d11 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.5-SNAPSHOT + 0.0.5 Mopo jar Java Playwright helpers for Vaadin users @@ -28,7 +28,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - HEAD + mopo-0.0.5 From de01c64e18534101467602f179f5dcb325f150ee Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 24 Jul 2025 15:34:50 +0300 Subject: [PATCH 07/25] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ba75d11..f0666ab 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.5 + 0.0.6-SNAPSHOT Mopo jar Java Playwright helpers for Vaadin users @@ -28,7 +28,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - mopo-0.0.5 + HEAD From a867807f2d07fd3d02d4b02bba5ec5168a0ff8da Mon Sep 17 00:00:00 2001 From: Simon Martinelli Date: Wed, 20 Aug 2025 09:43:29 +0200 Subject: [PATCH 08/25] Add comprehensive documentation for Mopo library usage examples. --- documentation.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 documentation.md diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..534b4ad --- /dev/null +++ b/documentation.md @@ -0,0 +1,201 @@ +# Mopo Documentation + +Mopo is a small helper library to make testing Vaadin applications with Microsoft Playwright easier from Java. +It provides Page Object helpers for complex Vaadin components (Grid, Date Picker, Date Time Picker, Combo Box) and +a set of utility methods to interact with Vaadin-specific client/server behavior. + +For installation and a short intro, see README.md. This document focuses on practical examples taken directly from the +integration tests in this repository. + +## Utilities: Mopo + +Mopo provides utilities around Playwright and Vaadin, such as: +- waitForConnectionToSettle(): waits for Vaadin client/server requests to settle +- click(Locator)/click(String): reliable clicking helper +- trackClientSideErrors(), getClientSideErrors(), failOnClientSideErrors(): console error tracking helpers +- assertNoJsErrors(): asserts dev tools shows no JS errors (when available in dev mode) +- getViewsReportedByDevMode(): list available routes reported by Vaadin DevTools +- driveIn(): execute code in the context of an element/shadow root + +Example usage (from AddonHelpersIT): + +```java +// Track JS console errors and ensure page stays clean +mopo.trackClientSideErrors(); +page.navigate("http://localhost:" + port + "/addonhelpers"); + +mopo.waitForConnectionToSettle(); + +mopo.getClientSideErrors().clear(); // clear expected favicon warning + +Assertions.assertEquals(0, mopo.getClientSideErrors().size(), + "There should be no console errors after the page has loaded"); + +// Trigger an exception on purpose and fail the test if there are errors +page.getByText("Throw JS exception").click(); +boolean thrown = false; +try { + mopo.failOnClientSideErrors(); +} catch (RuntimeException e) { + thrown = true; +} +Assertions.assertTrue(thrown, "There should be an exception thrown"); +``` + +List routes from Vaadin DevTools (dev mode): + +```java +List developmentTimeViewNames = mopo.getViewsReportedByDevMode(browser, "http://localhost:" + port + "/"); +developmentTimeViewNames.forEach(System.out::println); +``` + +Tip: When using inputs whose value is updated with server round-trips or lazy value-changed listeners, prefer calling +`mopo.waitForConnectionToSettle()` before asserting. + +## Grid: GridPw + +GridPw wraps vaadin-grid to provide convenient methods: +- getRenderedRowCount() +- getFirstVisibleRowIndex(), getLastVisibleRowIndex() +- scrollToIndex(int) +- selectRow(int) +- getRow(int).getCell(int | String headerText) +- RowPw.select() + +Example (adapted from GridPlaywrightIT): + +```java +page.navigate("http://localhost:" + port + "/grid"); +GridPw grid = new GridPw(page); + +// Basic scrolling API +assertEquals(0, grid.getFirstVisibleRowIndex()); +grid.scrollToIndex(10); +assertEquals(10, grid.getFirstVisibleRowIndex()); +grid.scrollToIndex(0); + +// Select first row and edit via a form bound to selection +String originalFirstName = grid.getRow(0).getCell(0).textContent(); +grid.getRow(0).select(); + +// Update the "First name" field in the edit form +String newFirstName = originalFirstName + "_changed0"; +page.getByLabel("First name", new Page.GetByLabelOptions().setExact(true)).fill(newFirstName); +page.getByText("Save").click(); + +mopo.waitForConnectionToSettle(); + +// Verify via both index and header +assertThat(grid.getRow(0).getCell(0)).hasText(newFirstName); +assertThat(grid.getRow(0).getCell("First Name")).hasText(newFirstName); +``` + +Filtering example showing lazy server round-trips: + +```java +page.getByPlaceholder("Filter by name...").locator("input").fill("Alice"); +// Filter input has lazy value change listener +mopo.waitForConnectionToSettle(); +assertEquals(1, grid.getRenderedRowCount()); +``` + +## Date Picker: DatePickerPw + +DatePickerPw helps reading/writing values of vaadin-date-picker using LocalDate. +- getValue(): LocalDate (returns null if field is empty or unparsable) +- setValue(LocalDate) +- getInputString(): raw input value as string (locale formatted) + +Example (from DatePickerIT): + +```java +page.navigate("http://localhost:" + port + "/date"); +DatePickerPw datePickerPw = new DatePickerPw(page.locator("#dp")); + +LocalDate localDate = LocalDate.of(2001, 12, 24); +assertNull(datePickerPw.getValue()); + +datePickerPw.setValue(localDate); +assertEquals(localDate, datePickerPw.getValue()); +assertThat(page.locator("#dpValue")).containsText(localDate.toString()); + +// The view has a button that sets the date to now on the server +LocalDate now = LocalDate.now(); +mopo.click(page.getByText("set now")); +String valueInField = datePickerPw.getInputString(); +String formattedNow = DateTimeFormatter.ofPattern("M/d/yyyy", Locale.US).format(now); +assertEquals(formattedNow, valueInField); +assertThat(page.locator("#dpValue")).containsText(now.toString()); +``` + +## Date Time Picker: DateTimePickerPw + +DateTimePickerPw (see source for methods) provides helpers for vaadin-date-time-picker, including reading the separate +date and time inputs as strings formatted according to locale. + +Example (from DatePickerIT): + +```java +DateTimePickerPw dateTimePickerPw = new DateTimePickerPw(page.locator("#dtp")); +LocalDateTime localDateTime = LocalDateTime.of(2001,12,24,22,36,0,0); + +dateTimePickerPw.setValue(localDateTime); +assertThat(page.locator("#dtpValue")).containsText(localDateTime.toString()); + +String dateInputValue = dateTimePickerPw.getDateInputString(); +String timeInputValue = dateTimePickerPw.getTimeInputString(); +assertEquals("12/24/2001", dateInputValue); +assertEquals("10:36:00 PM", timeInputValue); +``` + +## Combo Box: ComboBoxPw + +ComboBoxPw offers convenience around vaadin-combo-box: +- filter(String) +- filterAndSelectFirst(String) +- selectOption(String) +- openDropDown(): Locator +- selectionDropdown(): Locator + +Example usage (from ComboBoxIT): + +```java +page.navigate("http://localhost:" + port + "/combobox"); + +ComboBoxPw cbPw = new ComboBoxPw(page.locator("vaadin-combo-box")); +Locator value = page.locator("#value"); + +// Type filter and pick first suggestion +cbPw.filterAndSelectFirst("foo"); +assertThat(value).containsText("foo"); + +// Type partial and select a specific option from the overlay +cbPw.filter("ba").selectOption("baz"); +assertThat(value).containsText("baz"); + +// Open dropdown via toggle and click an option +cbPw.openDropDown().getByText("foo").click(); +assertThat(value).containsText("foo"); +``` + +Raw Playwright example (no helper) for comparison: + +```java +Locator cb = page.locator("input[role='combobox']"); +cb.fill("foo"); +page.waitForTimeout(1000); // sometimes needed with newer Vaadin versions +cb.press("Enter"); +``` + +## Tips + +- Prefer locating by ids or labels where possible: `page.getByLabel("First name", new Page.GetByLabelOptions().setExact(true))`. +- Vaadin components often update state via server round-trips. After interactions that trigger server-side logic, use `mopo.waitForConnectionToSettle()` before asserting. +- For combo box suggestions, ensure the overlay is visible and skip hidden duplicates: `vaadin-combo-box-item:not([hidden])`. +- Use Mopo.click() wrapper to avoid flakiness with certain shadow DOM elements. + +## References + +- Source helpers: src/main/java/in/virit/mopo/ +- Integration tests with examples: src/test/java/firitin/pw/ +- README: README.md From 021c962beb521ff188fff60e234f27c3f903d49a Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 27 Nov 2025 16:26:05 +0200 Subject: [PATCH 09/25] Vaadin 25 compatibility and version updates --- pom.xml | 24 ++++++++++++------- src/main/java/in/virit/mopo/ComboBoxPw.java | 2 +- .../java/in/virit/mopo/DateTimePickerPw.java | 3 +++ src/test/java/firitin/pw/ComboBoxIT.java | 2 +- src/test/java/firitin/ui/AppShell.java | 4 ++++ 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index f0666ab..34d59f2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,8 @@ Java Playwright helpers for Vaadin users - 24.8.4 - 17 - 17 + 25.0.0-beta7 + 21 UTF-8 UTF-8 @@ -47,7 +46,7 @@ org.junit junit-bom - 5.10.2 + 5.14.0 pom import @@ -65,24 +64,31 @@ com.vaadin vaadin-core + test + + + + com.vaadin + vaadin-dev + test com.microsoft.playwright playwright - 1.53.0 + 1.56.0 jakarta.servlet jakarta.servlet-api - 5.0.0 + 6.1.0 provided commons-beanutils commons-beanutils - 1.9.4 + 1.11.0 test jar @@ -90,7 +96,7 @@ org.hibernate.validator hibernate-validator - 8.0.0.Final + 9.1.0.Final test @@ -185,7 +191,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.6.0 + 0.9.0 true central diff --git a/src/main/java/in/virit/mopo/ComboBoxPw.java b/src/main/java/in/virit/mopo/ComboBoxPw.java index 36d39c0..687cca9 100644 --- a/src/main/java/in/virit/mopo/ComboBoxPw.java +++ b/src/main/java/in/virit/mopo/ComboBoxPw.java @@ -75,7 +75,7 @@ public ComboBoxPw selectOption(String option) { */ public Locator selectionDropdown() { // there can be only one - return root.page().locator("vaadin-combo-box-overlay"); + return root.page().locator("vaadin-combo-box-scroller"); } /** diff --git a/src/main/java/in/virit/mopo/DateTimePickerPw.java b/src/main/java/in/virit/mopo/DateTimePickerPw.java index 8b72122..ab78930 100644 --- a/src/main/java/in/virit/mopo/DateTimePickerPw.java +++ b/src/main/java/in/virit/mopo/DateTimePickerPw.java @@ -28,6 +28,9 @@ public DateTimePickerPw(Locator gridLocator) { */ public void setValue(LocalDateTime value) { root.evaluate("db => db.value = '%s'".formatted(value)); + // needed since 25... + root.evaluate("db => db.querySelector(\"input\").focus()"); + root.evaluate("db => db.querySelector(\"input\").blur()"); } /** diff --git a/src/test/java/firitin/pw/ComboBoxIT.java b/src/test/java/firitin/pw/ComboBoxIT.java index 5eb7f05..5661cc6 100644 --- a/src/test/java/firitin/pw/ComboBoxIT.java +++ b/src/test/java/firitin/pw/ComboBoxIT.java @@ -66,7 +66,7 @@ public void rawUsage() throws InterruptedException { assertThat(value).containsText("foo"); cb.fill("ba"); - Locator overlay = page.locator("vaadin-combo-box-overlay"); + Locator overlay = page.locator("vaadin-combo-box-scroller"); // this should be third option & visible overlay.getByText("baz").click(); diff --git a/src/test/java/firitin/ui/AppShell.java b/src/test/java/firitin/ui/AppShell.java index 4b11901..38f965d 100644 --- a/src/test/java/firitin/ui/AppShell.java +++ b/src/test/java/firitin/ui/AppShell.java @@ -1,9 +1,13 @@ package firitin.ui; +import com.vaadin.flow.component.dependency.StyleSheet; import com.vaadin.flow.component.page.AppShellConfigurator; import com.vaadin.flow.component.page.Push; +import com.vaadin.flow.theme.lumo.Lumo; @Push +@StyleSheet(Lumo.STYLESHEET) +@StyleSheet(Lumo.UTILITY_STYLESHEET) public class AppShell implements AppShellConfigurator { } \ No newline at end of file From 837475e1a062b75466c8a51e9894b73b85a748b9 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 27 Nov 2025 16:26:55 +0200 Subject: [PATCH 10/25] Use java21 for ci --- .github/workflows/ci-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 071a8d0..53a33e8 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -9,10 +9,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Build with Maven run: mvn --batch-mode --update-snapshots verify -Pit From 1d34fb85c708125470b6749b31ef5785a806aabd Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 27 Nov 2025 16:31:05 +0200 Subject: [PATCH 11/25] [maven-release-plugin] prepare release mopo-0.0.6 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 34d59f2..f9801d8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.6-SNAPSHOT + 0.0.6 Mopo jar Java Playwright helpers for Vaadin users @@ -27,7 +27,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - HEAD + mopo-0.0.6 From 9814e2649eb1168654b3bc9ff241fa5eb0bfbd23 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 27 Nov 2025 16:31:09 +0200 Subject: [PATCH 12/25] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f9801d8..e646450 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 in.virit mopo - 0.0.6 + 0.0.7-SNAPSHOT Mopo jar Java Playwright helpers for Vaadin users @@ -27,7 +27,7 @@ https://github.com/viritin/mopo scm:git:git://github.com/viritin/mopo.git scm:git:ssh://git@github.com:/viritin/mopo.git - mopo-0.0.6 + HEAD From 171f33b1df131ec9fe62022f60bed8c6ccb987df Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 27 Nov 2025 16:33:33 +0200 Subject: [PATCH 13/25] Update mopo dependency version in README Updated the version of the mopo dependency to 0.0.6. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 556c259..f94bf3c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ To try/use Mopo, add following dependency to your pom.xml (or to Gradle build): in.virit mopo - 0.0.1 + test + com.vaadin @@ -82,7 +89,7 @@ jakarta.servlet jakarta.servlet-api - 6.1.0 + provided @@ -132,6 +139,11 @@ formatter-maven-plugin 2.12.2 + + org.springframework.boot + spring-boot-maven-plugin + + org.apache.maven.plugins maven-jar-plugin @@ -340,4 +352,3 @@ - diff --git a/src/test/java/firitin/TestRunner.java b/src/test/java/firitin/TestRunner.java new file mode 100644 index 0000000..82fd4c1 --- /dev/null +++ b/src/test/java/firitin/TestRunner.java @@ -0,0 +1,24 @@ +package firitin; +import java.util.Collections; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +// This annotation tells Spring to start up and look for Views and AppShell +@SpringBootApplication +public class TestRunner { + + public static void main(String[] args) { + // This launches the web server on localhost:9998 + SpringApplication app = new SpringApplication(TestRunner.class); + + // Force port 9998 + app.setDefaultProperties(Collections.singletonMap("server.port", "9998")); + + app.run(args); + + System.out.println("----------------------------------------------"); + System.out.println(" Test Server Running at http://localhost:9998"); + System.out.println("----------------------------------------------"); + } +} From 10161fcef74c404f353170e57190c5f216788a19 Mon Sep 17 00:00:00 2001 From: Augusto Duarte Date: Fri, 26 Dec 2025 10:34:23 -0300 Subject: [PATCH 24/25] fix: use code from original repo Use DatePickerPW from original repo to pass DatePickerIT. Use GridPw.getCell() from original repo to pass GridPlaywrightIT. --- src/main/java/in/virit/mopo/DatePickerPw.java | 74 +++++-------------- src/main/java/in/virit/mopo/GridPw.java | 28 ++++--- 2 files changed, 34 insertions(+), 68 deletions(-) diff --git a/src/main/java/in/virit/mopo/DatePickerPw.java b/src/main/java/in/virit/mopo/DatePickerPw.java index c917e71..369b297 100644 --- a/src/main/java/in/virit/mopo/DatePickerPw.java +++ b/src/main/java/in/virit/mopo/DatePickerPw.java @@ -1,9 +1,8 @@ package in.virit.mopo; -import com.microsoft.playwright.Locator; -import com.microsoft.playwright.Page; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; + +import com.microsoft.playwright.Locator; /** * A helper class to work with vaadin-date-picker component. @@ -11,22 +10,7 @@ public class DatePickerPw { private final Locator root; - private final Page page; - private String dateFormat; - /** - * Creates a DatePicker page object for the specified Page and element ID. - * - * @param page - * The Page to which the date picker belongs. - * @param id - * The ID of the date picker element. - */ - public DatePickerPw(Page page, String id) { - this.page = page; - this.root = page.locator("#" + id + " > input"); - } - /** * Creates a DatePicker page object for the given locator. * @@ -35,7 +19,6 @@ public DatePickerPw(Page page, String id) { */ public DatePickerPw(Locator gridLocator) { this.root = gridLocator; - this.page = gridLocator.page(); } /** @@ -47,48 +30,27 @@ public DatePickerPw(Locator gridLocator) { public LocalDate getValue() { String str = (String) root.evaluate("db => db.value"); try { - return LocalDate.parse(str, DateTimeFormatter.ofPattern(dateFormat)); - } catch (java.time.format.DateTimeParseException e) { + return LocalDate.parse(str); + } catch (java.time.format.DateTimeParseException e) { return null; } } - /** - * Sets the value of the field. - * - * @param value - * the value to be set - */ - public void setValue(LocalDate value) { - String formattedDate = (dateFormat == null ? value.toString() - : DateTimeFormatter.ofPattern(dateFormat).format(value)); - - root.fill(formattedDate); - root.press("Enter"); - - // Wait for the date picker overlay to be hidden - Page.WaitForSelectorOptions options = new Page.WaitForSelectorOptions(); - options.setState(options.state.HIDDEN); - page.waitForSelector("vaadin-date-picker-overlay", options); - } - - /** - * Sets the format to be used when setting the date value. - * - * @param format - * The format to be set. - */ - public void setDateFormat(String format) { - dateFormat = format; + /** + * Sets the value of the field. + * + * @param value the value to be set + */ + public void setValue(LocalDate value) { + root.evaluate("db => db.value = '%s'".formatted(value)); } - /** - * Returns the raw string value in the field. - * - * @return the string value as it is formatted in the field. Note, this may - * be locale dependent. - */ - public String getInputString() { + /** + * Returns the raw string value in the field. + * + * @return the string value as it is formatted in the field. Note, this may be locale dependent. + */ + public String getInputString() { return root.locator("input").inputValue(); } -} +} \ No newline at end of file diff --git a/src/main/java/in/virit/mopo/GridPw.java b/src/main/java/in/virit/mopo/GridPw.java index ab14837..959cf13 100644 --- a/src/main/java/in/virit/mopo/GridPw.java +++ b/src/main/java/in/virit/mopo/GridPw.java @@ -1,10 +1,11 @@ package in.virit.mopo; -import com.microsoft.playwright.Locator; -import com.microsoft.playwright.Page; import java.util.ArrayList; import java.util.List; +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; + /** * A helper class to work with the vaadin-grid component. */ @@ -204,18 +205,21 @@ private RowPw(int rowIndex) { * @return the cell locator */ public Locator getCell(int cellIndex) { - int indexInVirtualTable = (Integer) root.evaluate( - "g => g._getRenderedRows().indexOf(g._getRenderedRows().filter(r => r.index == %s)[0]);" - .formatted(rowIndex)); - indexInVirtualTable += 1; // 1-based :-) - String name = root - .locator("#items tr:nth-child(%s) td:nth-child(%s) slot" - .formatted(indexInVirtualTable, cellIndex + 1)) - .getAttribute("name"); - return root.locator( - "vaadin-grid-cell-content[slot='%s']".formatted(name)); + int indexInVirtualTable = + (Integer) + root.evaluate( + "g => g._getRenderedRows().indexOf(g._getRenderedRows().filter(r => r.index == %s)[0]);" + .formatted(rowIndex)); + Locator row = + root.locator("#items tr") + .filter(new Locator.FilterOptions().setVisible(true)) + .nth(indexInVirtualTable); + String name = + row.locator("td:nth-child(%s) slot".formatted(cellIndex + 1)).getAttribute("name"); + return root.locator("vaadin-grid-cell-content[slot='%s']".formatted(name)); } + /** * Gets the cell with the given header text. * From e2a202eadabd94b989c5756b3465405b73b9dc23 Mon Sep 17 00:00:00 2001 From: Augusto Duarte Date: Fri, 26 Dec 2025 11:40:22 -0300 Subject: [PATCH 25/25] test: use original repo's DateTimePW constructor in test --- src/test/java/firitin/pw/DatePickerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/firitin/pw/DatePickerIT.java b/src/test/java/firitin/pw/DatePickerIT.java index 80e099c..ef9f4c5 100644 --- a/src/test/java/firitin/pw/DatePickerIT.java +++ b/src/test/java/firitin/pw/DatePickerIT.java @@ -61,7 +61,7 @@ public void doStuffWithDatePickerPw() { LocalDate localDate = LocalDate.of(2001,12,24); - DatePickerPw datePickerPw = new DatePickerPw(page, "dp"); + DatePickerPw datePickerPw = new DatePickerPw(page.locator("#dp")); LocalDate value = datePickerPw.getValue(); assertNull(value);