From 8f6a974fbe40056c9e55e4423a3988e7f8b9cb5d Mon Sep 17 00:00:00 2001 From: Simon Laden Date: Tue, 14 Jul 2026 16:23:06 +0100 Subject: [PATCH] FISH-12503 - add Playwright tests + clean up code --- ecosystem/testcontainers-example/README.md | 19 +- ecosystem/testcontainers-example/pom.xml | 33 ++- .../fish/payara/examples/domain/Book.java | 70 +++--- .../payara/examples/domain/Librarian.java | 80 ++++--- .../fish/payara/examples/domain/Loan.java | 63 +++--- .../fish/payara/examples/domain/Patron.java | 69 +++--- .../payara/examples/resource/BookBean.java | 4 +- .../examples/resource/LibrarianBean.java | 7 +- .../payara/examples/resource/PatronBean.java | 4 +- .../src/main/webapp/book.xhtml | 6 +- .../src/main/webapp/librarian.xhtml | 6 +- .../src/main/webapp/patron.xhtml | 6 +- .../examples/service/AbstractServiceIT.java | 43 ++++ .../examples/service/BookServiceIT.java | 68 ++---- .../examples/service/LibrarianServiceIT.java | 48 +---- .../examples/service/PatronServiceIT.java | 45 ++-- .../testcontainers/AbstractContainerIT.java | 63 ++++++ .../testcontainers/PayaraMicroContainer.java | 41 +++- .../fish/payara/examples/ui/AbstractUiIT.java | 199 ++++++++++++++++++ .../fish/payara/examples/ui/BookUiIT.java | 68 ++++++ .../payara/examples/ui/LibrarianUiIT.java | 63 ++++++ .../fish/payara/examples/ui/LoanUiIT.java | 103 +++++++++ .../fish/payara/examples/ui/PatronUiIT.java | 67 ++++++ 23 files changed, 921 insertions(+), 254 deletions(-) create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/service/AbstractServiceIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/testcontainers/AbstractContainerIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/ui/AbstractUiIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/ui/BookUiIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/ui/LibrarianUiIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/ui/LoanUiIT.java create mode 100644 ecosystem/testcontainers-example/src/test/java/fish/payara/examples/ui/PatronUiIT.java diff --git a/ecosystem/testcontainers-example/README.md b/ecosystem/testcontainers-example/README.md index 07c7e0d..d4f1ee0 100644 --- a/ecosystem/testcontainers-example/README.md +++ b/ecosystem/testcontainers-example/README.md @@ -34,12 +34,29 @@ To run the tests for the application locally, follow these steps: 2. Make sure you have the appropriate Java version installed. -3. Execute the following command: +3. Make sure [Docker](https://www.docker.com/products/docker-desktop/) is installed and running. The integration + tests (`*IT.java`) use [Testcontainers](https://testcontainers.com/) to start a real Payara Micro instance with + the application deployed, so Docker needs to be reachable from the JVM running the tests. If you hit + `Could not find a valid Docker environment` on Windows even though `docker` works fine from the shell, see + [Testcontainers' Windows troubleshooting guide](https://java.testcontainers.org/supported_docker_environment/windows/). + +4. Execute the following command: ``` ./mvn clean verify ``` +### What gets tested + +- `*ServiceIT` (`fish.payara.examples.service`) — REST API tests hitting the deployed application directly with a + JAX-RS client. +- `*UiIT` (`fish.payara.examples.ui`) — browser tests driving the JSF pages (Book, Patron, Librarian, Loan) end to + end with [Playwright](https://playwright.dev/java/). The Playwright Chromium browser binary is installed + automatically as part of the build (see the `exec-maven-plugin` execution in `pom.xml`) — no separate setup step + is needed. + +Both sets of IT tests share a single Payara Micro Testcontainer for the whole test run (see +`fish.payara.examples.testcontainers.AbstractContainerIT`), so it's started once instead of once per test class. diff --git a/ecosystem/testcontainers-example/pom.xml b/ecosystem/testcontainers-example/pom.xml index 807133d..f174f6f 100644 --- a/ecosystem/testcontainers-example/pom.xml +++ b/ecosystem/testcontainers-example/pom.xml @@ -61,7 +61,7 @@ org.testcontainers junit-jupiter - 1.21.1 + 1.21.4 test jar @@ -118,6 +118,35 @@ false + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + install-playwright-browsers + generate-test-resources + + exec + + + java + test + + -classpath + + com.microsoft.playwright.CLI + install + chromium + + + + + fish.payara.maven.plugins @@ -201,6 +230,8 @@ ${project.build.directory}/payara-micro-${payara.version}.jar + ${payara.version} + ${project.build.directory}/${project.build.finalName}.war diff --git a/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Book.java b/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Book.java index 8420f65..5934d1d 100644 --- a/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Book.java +++ b/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Book.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.List; +import java.util.UUID; import jakarta.json.bind.annotation.JsonbTransient; @NamedQueries({ @@ -15,10 +16,23 @@ @Entity public class Book { + // Plain @Id, no @GeneratedValue, with an isBlank() check in @PrePersist: + // see Librarian.assignId() for the full explanation - book.xhtml's + // "Isbn:" field is a plain h:inputText bound directly to isbn, so saving + // it blank submits "" (not null) for this String property, which needs + // the same isBlank() guard to avoid persisting a literal empty-string id. + // Note: this is a generated surrogate key, not a real ISBN - the field + // name predates this fix. @Id - @GeneratedValue(strategy = GenerationType.AUTO) private String isbn; + @PrePersist + private void assignId() { + if (isbn == null || isbn.isBlank()) { + isbn = UUID.randomUUID().toString(); + } + } + private String title; private String author; @@ -30,6 +44,33 @@ public class Book { private List loans; + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.isbn); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Book other = (Book) obj; + return Objects.equals(this.isbn, other.isbn); + } + + @Override + public String toString() { + return String.valueOf(title); + } + // Getters and setters public String getIsbn() { @@ -72,31 +113,4 @@ public void setLoans(List loans) { this.loans = loans; } - @Override - public int hashCode() { - int hash = 3; - hash = 97 * hash + Objects.hashCode(this.isbn); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Book other = (Book) obj; - return Objects.equals(this.isbn, other.isbn); - } - - @Override - public String toString() { - return String.valueOf(title); - } - } diff --git a/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Librarian.java b/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Librarian.java index 749d69c..1d9c9b4 100644 --- a/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Librarian.java +++ b/ecosystem/testcontainers-example/src/main/java/fish/payara/examples/domain/Librarian.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.List; +import java.util.UUID; import jakarta.json.bind.annotation.JsonbTransient; @NamedQueries({ @@ -14,10 +15,33 @@ @Entity public class Librarian { + // Plain @Id, no @GeneratedValue: JPA's generation strategies (IDENTITY/ + // SEQUENCE/TABLE/AUTO) target numeric surrogate keys; for a String @Id + // we assign it ourselves in @PrePersist instead. Checking isBlank() (not + // just null) matters: librarian.xhtml's "Librarian ID:" field is a plain + // h:inputText bound directly to this property, and unlike a numeric + // property (where JSF's implicit converter turns an empty submission into + // null), a String property gets the submitted value verbatim - so saving + // the form with that field left blank sets librarianID to "" rather than + // null. That empty string is not null, so it silently passed the create/ + // edit check as "already has an id" and, before this fix, also passed + // this method's null check unchanged - persisting the row with a literal + // empty-string id. It looked fine in this page's own list (name/actions + // still worked), but broke anything that keyed off "does this entity have + // an id yet": the loan.xhtml dropdown's converter treats "" the same as + // "no id" and renders it as an empty