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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4942f8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +/bin +.project 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 + vaadin-dev + test com.microsoft.playwright playwright - 1.44.0 + 1.56.0 jakarta.servlet jakarta.servlet-api - 5.0.0 + provided commons-beanutils commons-beanutils - 1.9.4 + 1.11.0 test jar @@ -101,7 +103,7 @@ org.hibernate.validator hibernate-validator - 8.0.0.Final + 9.1.0.Final test @@ -137,6 +139,11 @@ formatter-maven-plugin 2.12.2 + + org.springframework.boot + spring-boot-maven-plugin + + org.apache.maven.plugins maven-jar-plugin @@ -177,9 +184,12 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.1.1 + true + false release + deploy @@ -191,20 +201,19 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.9.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 +234,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 @@ -317,4 +352,3 @@ - diff --git a/src/main/java/in/virit/mopo/ComboBoxPw.java b/src/main/java/in/virit/mopo/ComboBoxPw.java index a132d50..9850b27 100644 --- a/src/main/java/in/virit/mopo/ComboBoxPw.java +++ b/src/main/java/in/virit/mopo/ComboBoxPw.java @@ -1,7 +1,7 @@ package in.virit.mopo; import com.microsoft.playwright.Locator; - +import java.util.List; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; /** @@ -28,7 +28,31 @@ 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", ""); + } + + /** + * Filters the MultiSelectComboBox by searching for each term provided in the filter + * list and selects the first suggestion for each term. + * + * @param filterList a list of strings representing the terms to be searched + * for and selected in the ComboBox + */ + public void filterAndSelectFirstMultiSelect(List filterList) { + root.locator("input").press("Escape"); + for(String filter: filterList) { + filter(filter); root.locator("input").press("Enter"); + root.locator("input").press("Escape"); + } + root.locator("input").press("Escape"); } /** @@ -68,7 +92,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"); } /** @@ -80,5 +104,14 @@ public Locator openDropDown() { root.locator("#toggleButton").click(); return selectionDropdown(); } + + /** + * Returns the selected value for the ComboBox + * + * @return the selected value + */ + public String getSelection() { + return root.locator("input").inputValue(); + } } diff --git a/src/main/java/in/virit/mopo/DatePickerPw.java b/src/main/java/in/virit/mopo/DatePickerPw.java index 15152ba..369b297 100644 --- a/src/main/java/in/virit/mopo/DatePickerPw.java +++ b/src/main/java/in/virit/mopo/DatePickerPw.java @@ -1,9 +1,9 @@ package in.virit.mopo; -import com.microsoft.playwright.Locator; - import java.time.LocalDate; +import com.microsoft.playwright.Locator; + /** * A helper class to work with vaadin-date-picker component. */ @@ -30,28 +30,27 @@ public DatePickerPw(Locator gridLocator) { public LocalDate getValue() { String str = (String) root.evaluate("db => db.value"); try { - return LocalDate.parse(str); - } 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) { - root.evaluate("db => db.value = '%s'".formatted(value)); + /** + * 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/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/main/java/in/virit/mopo/GridPw.java b/src/main/java/in/virit/mopo/GridPw.java index b6f58e4..959cf13 100644 --- a/src/main/java/in/virit/mopo/GridPw.java +++ b/src/main/java/in/virit/mopo/GridPw.java @@ -1,5 +1,8 @@ package in.virit.mopo; +import java.util.ArrayList; +import java.util.List; + import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; @@ -13,7 +16,8 @@ public class GridPw { /** * Creates a Grid page object for the given grid locator. * - * @param gridLocator the Playwright locator for the grid + * @param gridLocator + * the Playwright locator for the grid */ public GridPw(Locator gridLocator) { this.root = gridLocator; @@ -22,7 +26,8 @@ public GridPw(Locator gridLocator) { /** * Creates a Grid page object for the first grid on the page. * - * @param page the Playwright page + * @param page + * the Playwright page */ public GridPw(Page page) { this(page.locator("vaadin-grid")); @@ -34,78 +39,95 @@ public GridPw(Page page) { * @return the number of rows */ public int getRenderedRowCount() { - Integer evaluate = (Integer) root.elementHandle().evaluate("e => e._getRenderedRows().length"); + Integer evaluate = (Integer) root.elementHandle() + .evaluate("e => e._getRenderedRows().length"); return evaluate; } + /** + * Returns a list of RowPw objects representing the grid rows. + * + * @return A List of RowPw objects representing the grid rows. + */ + public List getGridRows() { + List allRows = new ArrayList<>(); + for (int i = 0; i < getRenderedRowCount(); i++) { + allRows.add(getRow(i)); + } + return allRows; + } + /** * Returns the index of the first visible row * * @return the index. */ public int getFirstVisibleRowIndex() { - return (Integer) root.elementHandle().evaluate("e => e._firstVisibleIndex"); + return (Integer) root.elementHandle() + .evaluate("e => e._firstVisibleIndex"); } /** * Scrolls the grid to the given index. * - * @param index the row index to scroll to + * @param index + * the row index to scroll to */ public void scrollToIndex(int index) { root.elementHandle().evaluate("e => e.scrollToIndex(" + index + ")"); // FIXME this don't seem to be stable, but // 10*100ms timeout seems to do the trick most of the time - int value = (Integer) root.evaluate(""" - g => { - return new Promise((resolve, reject) => { - var x = 0; - const isDoneLoading = () => { - return !g.$connector.hasRootRequestQueue() - }; - - const isVaadinConnectionActive = () => { - if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.clients) { - var clients = window.Vaadin.Flow.clients; - for (var client in clients) { - if (clients[client].isActive()) { - return false; + int value = (Integer) root.evaluate( + """ + g => { + return new Promise((resolve, reject) => { + var x = 0; + const isDoneLoading = () => { + return !g.$connector.hasRootRequestQueue() + }; + + const isVaadinConnectionActive = () => { + if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.clients) { + var clients = window.Vaadin.Flow.clients; + for (var client in clients) { + if (clients[client].isActive()) { + return false; + } + } + return true; + } else if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.devServerIsNotLoaded) { + return false; + } else { + return true; + } + }; + + if (isDoneLoading() && !isVaadinConnectionActive()) { + resolve(x); + return; } - } - return true; - } else if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.devServerIsNotLoaded) { - return false; - } else { - return true; - } - }; - - if (isDoneLoading() && !isVaadinConnectionActive()) { - resolve(x); - return; - } - - var intervalID = window.setInterval(function () { - if (isDoneLoading() && !isVaadinConnectionActive()) { - window.clearInterval(intervalID); - resolve(x+1); - } else { - if (++x === 10) { - window.clearInterval(intervalID); - resolve(-1); - } - } - }, 100); - }); - }"""); - // System.out.println("RETURN value = " + value); + + var intervalID = window.setInterval(function () { + if (isDoneLoading() && !isVaadinConnectionActive()) { + window.clearInterval(intervalID); + resolve(x+1); + } else { + if (++x === 10) { + window.clearInterval(intervalID); + resolve(-1); + } + } + }, 100); + }); + }"""); } /** * Selects the given row. * - * @param rowIndex the row index to select + * @param rowIndex + * the row index to select */ public void selectRow(int rowIndex) { String script = """ @@ -117,16 +139,18 @@ public void selectRow(int rowIndex) { var row = rows[0]; grid.activeItem = row._item; } - """.formatted(rowIndex); + """ + .formatted(rowIndex); root.elementHandle().evaluate(script); } /** * Returns a RowPw helper representing the row defined by the given index. * - * @param rowIndex the row index + * @param rowIndex + * the row index * @return the RowPw for editing the UI state or to get cell locators for - * assertions. + * assertions. */ public RowPw getRow(int rowIndex) { if (!isRowInView(rowIndex)) { @@ -138,9 +162,10 @@ public RowPw getRow(int rowIndex) { /** * Checks if the given row is in the visible viewport. * - * @param rowIndex the row to check + * @param rowIndex + * the row to check * @return true if the row is at least partially in view, - * false otherwise + * false otherwise */ public boolean isRowInView(int rowIndex) { return (getFirstVisibleRowIndex() <= rowIndex @@ -153,7 +178,8 @@ public boolean isRowInView(int rowIndex) { * @return the index */ public int getLastVisibleRowIndex() { - return (Integer) root.elementHandle().evaluate("e => e._lastVisibleIndex"); + return (Integer) root.elementHandle() + .evaluate("e => e._lastVisibleIndex"); } /** @@ -172,23 +198,33 @@ private RowPw(int rowIndex) { /** * Gets the cell locator at the given index. * - * @param cellIndex the cell index (0-based, unlike the CSS nth-child - * selector, whose designer should be hung by the balls, in case they - * have any) + * @param cellIndex + * the cell index (0-based, unlike the CSS nth-child + * selector, whose designer should be hung by the balls, in + * case they have any) * @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. * - * @param headerText the header text + * @param headerText + * the header text * @return the cell locator */ public Locator getCell(String headerText) { diff --git a/src/main/java/in/virit/mopo/Mopo.java b/src/main/java/in/virit/mopo/Mopo.java index 107364f..10cc64b 100644 --- a/src/main/java/in/virit/mopo/Mopo.java +++ b/src/main/java/in/virit/mopo/Mopo.java @@ -16,6 +16,7 @@ public class Mopo { private final Page page; + private List clientSideErrors = new ArrayList<>(); /** * Constructs a new Mopo for given page @@ -40,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) { @@ -60,7 +61,9 @@ 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 #failOnClientSideErrors()} ()} and {@link #trackClientSideErrors()} that use browser console. */ + @Deprecated public static void assertNoJsErrors(Page page) { try { @@ -80,6 +83,33 @@ 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 -> { + if (msg.type().equals("error")) { + clientSideErrors.add(msg.text()); + } + }); + + page.onPageError(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. @@ -90,7 +120,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 #failOnClientSideErrors()} ()} and {@link #trackClientSideErrors()} that use browser console. */ + @Deprecated public void assertNoJsErrors() { assertNoJsErrors(page); } @@ -180,4 +213,19 @@ public void click(String selector) { page.locator(selector).click(); waitForConnectionToSettle(); } + + /** + * 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 (var msg : consoleErrors) { + sb.append(msg).append("\n"); + } + throw new RuntimeException("JS errors discovered: "+ sb.toString()); + } + } } diff --git a/src/main/java/in/virit/mopo/TimePickerPw.java b/src/main/java/in/virit/mopo/TimePickerPw.java new file mode 100644 index 0000000..22f772f --- /dev/null +++ b/src/main/java/in/virit/mopo/TimePickerPw.java @@ -0,0 +1,77 @@ +package in.virit.mopo; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +/** + * A helper class to work with vaadin-time-picker component. + */ +public class TimePickerPw { + + private final Locator root; + private final Page page; + private String timeFormat; + + /** + * Constructs a TimePicker instance with the specified Page and element ID. + * + * @param page + * The Page to which the time picker belongs. + * @param id + * The ID of the time picker element. + */ + public TimePickerPw(Page page, String id) { + this.page = page; + this.root = page.locator("#" + id + " > input"); + } + + /** + * Sets the value of the time picker to the specified LocalTime. The + * formatted time is entered into the input field, and "Enter" is pressed. + * Additionally, it waits for the time picker overlay to be hidden after + * setting the value. + * + * @param value + * The LocalTime to be set. + */ + public void setValue(LocalTime value) { + String formattedTime = (timeFormat == null ? value.toString() + : DateTimeFormatter.ofPattern(timeFormat, Locale.ROOT).format(value)); + + root.fill(formattedTime); + root.press("Enter"); + + // Wait for the time picker overlay to be hidden + Page.WaitForSelectorOptions options = new Page.WaitForSelectorOptions(); + options.setState(options.state.HIDDEN); + page.waitForSelector("vaadin-time-picker-overlay", options); + } + + /** + * Returns the value from the client side and parses it as + * {@link LocalDate} using a {@link DateTimeFormatter}. + * + * @return the current value of the field + */ + public LocalTime getValue() { + String value = root.inputValue(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(timeFormat, Locale.ROOT); + + return timeFormat == null ? LocalTime.parse(value) + : LocalTime.parse(value, formatter); + } + + /** + * Sets the format to be used when setting the time value. + * + * @param format + * The format to be set. + */ + public void setDateFormat(String format) { + timeFormat = format; + } + +} 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("----------------------------------------------"); + } +} diff --git a/src/test/java/firitin/pw/AddonHelpersIT.java b/src/test/java/firitin/pw/AddonHelpersIT.java index a0a823d..735531b 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; @@ -47,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,12 +66,41 @@ public void doRandomStuffAndChangeFirstRow() throws InterruptedException { assertThat(page.locator("vaadin-dev-tools>div.error")).isAttached(); } + @Test + public void checkJsErrorsViaConsole() throws InterruptedException { + // 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(); + + 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 + page.getByText("Throw JS exception").click(); + + assertThat(page.getByText("Error should have been thrown!")).isVisible(); + + // You could call this in the beginning of the test, but testing it is nasty + // mopo.failOnClientSideErrorsOnClose(); - @Test + // This is pretty much the same as above but inverted for testing + boolean thrown = false; + try { + mopo.failOnClientSideErrors(); + } catch (RuntimeException e) { + thrown = true; + } + Assertions.assertTrue(thrown, "There should be an exception thrown"); + } + + @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 + "/"); 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..5661cc6 100644 --- a/src/test/java/firitin/pw/ComboBoxIT.java +++ b/src/test/java/firitin/pw/ComboBoxIT.java @@ -59,12 +59,14 @@ 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"); 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/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!")); })); } } 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