From 262c359806cb2a3d7cb3d8e2e41afc3a85c4dc87 Mon Sep 17 00:00:00 2001 From: melloware Date: Fri, 25 Oct 2024 14:47:30 -0400 Subject: [PATCH 1/6] Allow Playwright in Runtime module --- .github/workflows/build.yml | 8 +- deployment/pom.xml | 35 ++++++-- .../deployment/PlaywrightProcessor.java | 14 +++ .../devui/PlaywrightDevUIProcessor.java | 33 +++++++ .../resources/dev-ui/qwc-playwright-card.js | 86 +++++++++++++++++++ .../test/PlaywrightDevModeTest.java | 23 +++++ .../playwright/test/PlaywrightTest.java | 23 +++++ integration-tests/pom.xml | 19 ++-- .../playwright/it/PlaywrightResource.java | 55 ++++++++++++ .../src/main/resources/application.properties | 1 - .../playwright/it/PlaywrightResourceIT.java | 7 ++ .../playwright/it/PlaywrightResourceTest.java | 21 +++++ pom.xml | 36 +++++--- runtime/pom.xml | 65 ++++++++++++++ .../resources/META-INF/quarkus-extension.yaml | 4 +- testing/pom.xml | 10 +-- 16 files changed, 392 insertions(+), 48 deletions(-) create mode 100644 deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java create mode 100644 deployment/src/main/java/io/quarkiverse/playwright/deployment/devui/PlaywrightDevUIProcessor.java create mode 100644 deployment/src/main/resources/dev-ui/qwc-playwright-card.js create mode 100644 deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightDevModeTest.java create mode 100644 deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightTest.java create mode 100644 integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java create mode 100644 integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceIT.java create mode 100644 integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceTest.java create mode 100644 runtime/pom.xml rename {testing => runtime}/src/main/resources/META-INF/quarkus-extension.yaml (71%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec550ed..67d527b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: -# os: [windows-latest, macos-latest, ubuntu-latest] + # os: [windows-latest, macos-latest, ubuntu-latest] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: @@ -53,7 +53,7 @@ jobs: cache: 'maven' - name: Build with Maven - run: mvn -B clean install -Dno-format + run: mvn -B clean install -Dno-format --no-transfer-progress - - name: Build with Maven (Native) - run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip \ No newline at end of file + #- name: Build with Maven (Native) + # run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress \ No newline at end of file diff --git a/deployment/pom.xml b/deployment/pom.xml index 97697a1..218fd35 100644 --- a/deployment/pom.xml +++ b/deployment/pom.xml @@ -1,6 +1,7 @@ 4.0.0 + io.quarkiverse.playwright quarkus-playwright-parent @@ -8,26 +9,42 @@ quarkus-playwright-deployment Quarkus Playwright - Deployment + + + io.quarkus + quarkus-arc-deployment + io.quarkiverse.playwright quarkus-playwright ${project.version} + + io.quarkus + quarkus-junit5-internal + test + + maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${quarkus.version} - - - + + + default-compile + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + diff --git a/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java new file mode 100644 index 0000000..30cff28 --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java @@ -0,0 +1,14 @@ +package io.quarkiverse.playwright.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class PlaywrightProcessor { + + private static final String FEATURE = "playwright"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } +} diff --git a/deployment/src/main/java/io/quarkiverse/playwright/deployment/devui/PlaywrightDevUIProcessor.java b/deployment/src/main/java/io/quarkiverse/playwright/deployment/devui/PlaywrightDevUIProcessor.java new file mode 100644 index 0000000..033a7fd --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/playwright/deployment/devui/PlaywrightDevUIProcessor.java @@ -0,0 +1,33 @@ +package io.quarkiverse.playwright.deployment.devui; + +import com.microsoft.playwright.Playwright; + +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.ExternalPageBuilder; +import io.quarkus.devui.spi.page.Page; + +/** + * Dev UI card for displaying important details such as the Playwright library version. + */ +public class PlaywrightDevUIProcessor { + + @BuildStep(onlyIf = IsDevelopment.class) + void createVersion(BuildProducer cardPageBuildItemBuildProducer) { + final CardPageBuildItem card = new CardPageBuildItem(); + + final ExternalPageBuilder versionPage = Page.externalPageBuilder("Playwright Version") + .icon("font-awesome-solid:tag") + .url("https://playwright.dev") + .doNotEmbed() + .staticLabel(Playwright.class.getPackage().getImplementationVersion()); + + card.addPage(versionPage); + + card.setCustomCard("qwc-playwright-card.js"); + + cardPageBuildItemBuildProducer.produce(card); + } +} \ No newline at end of file diff --git a/deployment/src/main/resources/dev-ui/qwc-playwright-card.js b/deployment/src/main/resources/dev-ui/qwc-playwright-card.js new file mode 100644 index 0000000..ec6f044 --- /dev/null +++ b/deployment/src/main/resources/dev-ui/qwc-playwright-card.js @@ -0,0 +1,86 @@ +import { LitElement, html, css} from 'lit'; +import { pages } from 'build-time-data'; +import 'qwc/qwc-extension-link.js'; + +const NAME = "Playwright"; +export class QwcPlaywrightCard extends LitElement { + + static styles = css` + .identity { + display: flex; + justify-content: flex-start; + } + + .description { + padding-bottom: 10px; + } + + .logo { + padding-bottom: 10px; + margin-right: 5px; + } + + .card-content { + color: var(--lumo-contrast-90pct); + display: flex; + flex-direction: column; + justify-content: flex-start; + padding: 2px 2px; + height: 100%; + } + + .card-content slot { + display: flex; + flex-flow: column wrap; + padding-top: 5px; + } + `; + + static properties = { + description: {type: String} + }; + + constructor() { + super(); + } + + connectedCallback() { + super.connectedCallback(); + } + + render() { + return html`
+
+ +
${this.description}
+
+ ${this._renderCardLinks()} +
+ `; + } + + _renderCardLinks(){ + return html`${pages.map(page => html` + + + `)}`; + } + +} +customElements.define('qwc-playwright-card', QwcPlaywrightCard); \ No newline at end of file diff --git a/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightDevModeTest.java b/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightDevModeTest.java new file mode 100644 index 0000000..5d36e4e --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightDevModeTest.java @@ -0,0 +1,23 @@ +package io.quarkiverse.playwright.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +public class PlaywrightDevModeTest { + + // Start hot reload (DevMode) test with your extension loaded + @RegisterExtension + static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void writeYourOwnDevModeTest() { + // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information + Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName()); + } +} diff --git a/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightTest.java b/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightTest.java new file mode 100644 index 0000000..861a066 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/playwright/test/PlaywrightTest.java @@ -0,0 +1,23 @@ +package io.quarkiverse.playwright.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class PlaywrightTest { + + // Start unit test with your extension loaded + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void writeYourOwnUnitTest() { + // Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information + Assertions.assertTrue(true, "Add some assertions to " + getClass().getName()); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index ce975e4..9a60037 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -8,22 +8,16 @@ quarkus-playwright-integration-tests Quarkus Playwright - Integration Tests - - - - io.quarkus.platform - quarkus-bom - ${quarkus.version} - pom - import - - - io.quarkus quarkus-rest + + io.quarkiverse.playwright + quarkus-playwright + ${project.version} + io.quarkiverse.web-bundler quarkus-web-bundler @@ -32,6 +26,7 @@ io.quarkiverse.qute.web quarkus-qute-web + 3.2.0 org.mvnpm @@ -47,7 +42,7 @@ io.quarkiverse.playwright - quarkus-playwright + quarkus-playwright-test ${project.version} test diff --git a/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java b/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java new file mode 100644 index 0000000..b6264eb --- /dev/null +++ b/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java @@ -0,0 +1,55 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package io.quarkiverse.playwright.it; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; + +@Path("/playwright") +@ApplicationScoped +public class PlaywrightResource { + // add some rest methods here + + @GET + public String google() { + String pageTitle = "Hello playwright"; + final BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions() + .setHeadless(true) + .setChromiumSandbox(false) + .setChannel("") + .setArgs(List.of("--disable-gpu")); + final Map env = new HashMap<>(System.getenv()); + try (Playwright playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env))) { + try (Browser browser = playwright.chromium().launch(launchOptions)) { + Page page = browser.newPage(); + page.navigate("https://www.google.com/"); + pageTitle = page.title(); + } + } + return pageTitle; + } +} \ No newline at end of file diff --git a/integration-tests/src/main/resources/application.properties b/integration-tests/src/main/resources/application.properties index ad28d8e..e69de29 100644 --- a/integration-tests/src/main/resources/application.properties +++ b/integration-tests/src/main/resources/application.properties @@ -1 +0,0 @@ -quarkus.qsp.root-path=/ \ No newline at end of file diff --git a/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceIT.java b/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceIT.java new file mode 100644 index 0000000..4d4f11e --- /dev/null +++ b/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkiverse.playwright.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class PlaywrightResourceIT extends PlaywrightResourceTest { +} diff --git a/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceTest.java b/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceTest.java new file mode 100644 index 0000000..2edb4a2 --- /dev/null +++ b/integration-tests/src/test/java/io/quarkiverse/playwright/it/PlaywrightResourceTest.java @@ -0,0 +1,21 @@ +package io.quarkiverse.playwright.it; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class PlaywrightResourceTest { + + @Test + public void testGoogleEndpoint() { + given() + .when().get("/playwright") + .then() + .statusCode(200) + .body(is("Google")); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 49ea96e..9166a4f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,15 +11,16 @@ 999-SNAPSHOT pom Quarkus Playwright - Parent + Playwright enables reliable end-to-end testing for modern web apps - + deployment + runtime testing scm:git:git@github.com:quarkiverse/quarkus-playwright.git scm:git:git@github.com:quarkiverse/quarkus-playwright.git https://github.com/quarkiverse/quarkus-playwright - HEAD 3.13.0 @@ -38,6 +39,22 @@ pom import + + com.microsoft.playwright + playwright + ${playright.version} + + + org.opentest4j + opentest4j + + + + + com.microsoft.playwright + driver-bundle + ${playright.version} + @@ -49,20 +66,17 @@ ${quarkus.version} - io.quarkus - quarkus-config-doc-maven-plugin - ${version.quarkus} - - - org.apache.maven.plugins maven-compiler-plugin ${compiler-plugin.version} - - -parameters - + true + + io.quarkus + quarkus-config-doc-maven-plugin + ${quarkus.version} + diff --git a/runtime/pom.xml b/runtime/pom.xml new file mode 100644 index 0000000..ffffcec --- /dev/null +++ b/runtime/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + io.quarkiverse.playwright + quarkus-playwright-parent + 999-SNAPSHOT + + quarkus-playwright + Quarkus Playwright - Runtime + + + + io.quarkus + quarkus-arc + + + com.microsoft.playwright + playwright + + + com.microsoft.playwright + driver-bundle + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + default-compile + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + + diff --git a/testing/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml similarity index 71% rename from testing/src/main/resources/META-INF/quarkus-extension.yaml rename to runtime/src/main/resources/META-INF/quarkus-extension.yaml index ec0bb49..95f5c46 100644 --- a/testing/src/main/resources/META-INF/quarkus-extension.yaml +++ b/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -7,8 +7,8 @@ metadata: - e2e - end-to-end - playwright - guide: https://quarkiverse.github.io/quarkiverse-docs/playwright/dev/ # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension + guide: https://quarkiverse.github.io/quarkiverse-docs/playwright/dev/ icon-url: "https://playwright.dev/java/img/playwright-logo.svg" categories: - "testing" - status: "stable" + status: "stable" \ No newline at end of file diff --git a/testing/pom.xml b/testing/pom.xml index 919da3a..973dcff 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -6,24 +6,16 @@ quarkus-playwright-parent 999-SNAPSHOT - quarkus-playwright + quarkus-playwright-test Quarkus Playwright - Testing com.microsoft.playwright playwright - ${playright.version} - - - org.opentest4j - opentest4j - - com.microsoft.playwright driver-bundle - ${playright.version} io.quarkus From 5ac01167aadb6691ec3762b7c5947928d83ab938 Mon Sep 17 00:00:00 2001 From: melloware Date: Sat, 26 Oct 2024 11:33:44 -0400 Subject: [PATCH 2/6] Native Build work --- .github/workflows/build.yml | 4 +- README.md | 51 ++++++- .../deployment/PlaywrightProcessor.java | 127 +++++++++++++++++- docs/modules/ROOT/pages/index.adoc | 58 ++++++-- integration-tests/pom.xml | 58 +++++++- .../src/main/docker/Dockerfile.native | 28 ++++ .../playwright/it/PlaywrightResource.java | 11 +- .../runtime/PlaywrightRecorder.java | 62 +++++++++ .../resources/META-INF/quarkus-extension.yaml | 6 +- .../playwright/InjectPlaywright.java | 32 ++++- .../playwright/QuarkusPlaywrightManager.java | 86 +++++++++++- .../playwright/WithPlaywright.java | 98 +++++++++++--- 12 files changed, 571 insertions(+), 50 deletions(-) create mode 100644 integration-tests/src/main/docker/Dockerfile.native create mode 100644 runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67d527b..2a3a214 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,5 +55,5 @@ jobs: - name: Build with Maven run: mvn -B clean install -Dno-format --no-transfer-progress - #- name: Build with Maven (Native) - # run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress \ No newline at end of file + - name: Build with Maven (Native) + run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress \ No newline at end of file diff --git a/README.md b/README.md index e962a3e..87f58b1 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,23 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0) [![Build](https://github.com/quarkiverse/quarkus-playwright/actions/workflows/build.yml/badge.svg)](https://github.com/quarkiverse/quarkus-playwright/actions/workflows/build.yml) +[Playwright](https://playwright.dev/) is an open-source automation library designed for browser testing and web scraping. This extension supports two primary use cases: -Easily create effective cross-browsers e2e tests for your Quarkus web-app using Playwright (Qute, Quinoa, Renarde, Web-Bundler, ...): +1. **Testing:** Perform end-to-end tests for your Quarkus web application. +2. **Runtime:** Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation. All the information you need to use Quarkus Playwright is in the [user documentation](https://docs.quarkiverse.io/quarkus-playwright/dev/). -## Usage -Add to pom.xml: +## Test Usage + +The primary use case for Playwright is integration with `@QuarkusTest` for end-to-end testing of your application. You can easily create effective cross-browser end-to-end tests for your Quarkus web application using Playwright with frameworks such as Qute, Quinoa, Renarde, Web-Bundler, and MyFaces. Playwright Test was specifically designed to meet the requirements of end-to-end testing. It supports all modern rendering engines, including Chromium, WebKit, and Firefox. You can run tests on Windows, Linux, and macOS—either locally or in CI—both in headless and headed modes, with native mobile emulation for Google Chrome on Android and Mobile Safari. + + +Just add the `test` dependency to pom.xml: ```xml io.quarkiverse.playwright - quarkus-playwright + quarkus-playwright-test ${playwright.version} test @@ -55,9 +61,44 @@ public class WithDefaultPlaywrightTest { } ```` +Use the annotation `@WithPlaywright()` to change the browser (Chromium, Firefox, Webkit), headless, enable debug, logs and other options. + Debug your tests with the Playwright inspector `@WithPlaywright(debug=true)`: ![Debug](https://github.com/quarkiverse/quarkus-playwright/blob/main/docs/modules/ROOT/assets/images/playwright-debug.gif) + +## Runtime Usage + +Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation. + +Just add the `runtime` dependency to pom.xml: +```xml + + io.quarkiverse.playwright + quarkus-playwright + ${playwright.version} + +``` + +## Native + +If you plan on running in a Docker image we highly recommend you use a pre-built image from Microsoft `mcr.microsoft.com/playwright:v1.48.1` which is based on Ubuntu and already has all libraries and tools necessary for PlayWright. + +```yaml +FROM mcr.microsoft.com/playwright:v1.48.1-noble +WORKDIR /work/ +RUN chown 1001:root /work \ + && chmod g+rwX /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*.properties target/*.so /work/ +COPY --chown=1001:root target/*-runner /work/application +# Make application executable for all users +RUN chmod ugo+x /work/application +EXPOSE 8080 +USER 1001 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] +``` + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -87,4 +128,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java index 30cff28..c88ba00 100644 --- a/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java @@ -1,7 +1,36 @@ package io.quarkiverse.playwright.deployment; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.impl.driver.jar.DriverJar; +import com.microsoft.playwright.options.HttpHeader; +import com.microsoft.playwright.options.Timing; +import com.microsoft.playwright.options.ViewportSize; + +import io.quarkiverse.playwright.runtime.PlaywrightRecorder; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourcePatternsBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.logging.Log; class PlaywrightProcessor { @@ -11,4 +40,100 @@ class PlaywrightProcessor { FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } -} + + @BuildStep + void indexTransitiveDependencies(BuildProducer index) { + index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "driver")); + index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "driver-bundle")); + index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "playwright")); + } + + @BuildStep + NativeImageEnableAllCharsetsBuildItem enableAllCharsetsBuildItem() { + return new NativeImageEnableAllCharsetsBuildItem(); + } + + @BuildStep + void registerForReflection(CombinedIndexBuildItem combinedIndex, BuildProducer reflectiveClass) { + //@formatter:off + final List classNames = new ArrayList<>(); + + classNames.add("com.microsoft.playwright.impl.Message"); + classNames.add("com.microsoft.playwright.impl.SerializedArgument"); + classNames.add("com.microsoft.playwright.impl.SerializedValue"); + classNames.add("com.microsoft.playwright.impl.SerializedValue$O"); + classNames.add(Browser.CloseOptions.class.getName()); + classNames.add(Browser.NewContextOptions.class.getName()); + classNames.add(Browser.NewPageOptions.class.getName()); + classNames.add(Browser.StartTracingOptions.class.getName()); + classNames.add(DriverJar.class.getName()); + classNames.add(ElementHandle.CheckOptions.class.getName()); + classNames.add(ElementHandle.ClickOptions.class.getName()); + classNames.add(ElementHandle.DblclickOptions.class.getName()); + classNames.add(ElementHandle.FillOptions.class.getName()); + classNames.add(ElementHandle.HoverOptions.class.getName()); + classNames.add(ElementHandle.InputValueOptions.class.getName()); + classNames.add(ElementHandle.PressOptions.class.getName()); + classNames.add(ElementHandle.ScreenshotOptions.class.getName()); + classNames.add(ElementHandle.ScrollIntoViewIfNeededOptions.class.getName()); + classNames.add(ElementHandle.SelectTextOptions.class.getName()); + classNames.add(ElementHandle.SetInputFilesOptions.class.getName()); + classNames.add(ElementHandle.TapOptions.class.getName()); + classNames.add(ElementHandle.TypeOptions.class.getName()); + classNames.add(ElementHandle.UncheckOptions.class.getName()); + classNames.add(ElementHandle.WaitForElementStateOptions.class.getName()); + classNames.add(ElementHandle.WaitForSelectorOptions.class.getName()); + classNames.add(HttpHeader.class.getName()); + classNames.add(Timing.class.getName()); + classNames.add(ViewportSize.class.getName()); + classNames.addAll(collectImplementors(combinedIndex, Playwright.class.getName())); + + //@formatter:on + final TreeSet uniqueClasses = new TreeSet<>(classNames); + Log.debugf("Playwright Reflection: %s", uniqueClasses); + + reflectiveClass.produce( + ReflectiveClassBuildItem.builder(uniqueClasses.toArray(new String[0])).constructors().methods().fields() + .serialization().unsafeAllocated().build()); + } + + @BuildStep(onlyIf = IsNormal.class) + @Record(ExecutionTime.RUNTIME_INIT) + void registerRuntimeDrivers(PlaywrightRecorder recorder) { + recorder.initialize(); + } + + @BuildStep(onlyIf = IsNormal.class) + void registerNativeDrivers(BuildProducer nativeImageResourcePatterns) { + final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder(); + builder.includeGlob("driver/**"); + nativeImageResourcePatterns.produce(builder.build()); + } + + private List collectSubclasses(CombinedIndexBuildItem combinedIndex, String className) { + List classes = combinedIndex.getIndex() + .getAllKnownSubclasses(DotName.createSimple(className)) + .stream() + .map(ClassInfo::toString) + .collect(Collectors.toList()); + classes.add(className); + Log.debugf("Subclasses: %s", classes); + return classes; + } + + private List collectImplementors(CombinedIndexBuildItem combinedIndex, String className) { + Set classes = combinedIndex.getIndex() + .getAllKnownImplementors(DotName.createSimple(className)) + .stream() + .map(ClassInfo::toString) + .collect(Collectors.toCollection(HashSet::new)); + classes.add(className); + Set subclasses = new HashSet<>(); + for (String implementationClass : classes) { + subclasses.addAll(collectSubclasses(combinedIndex, implementationClass)); + } + classes.addAll(subclasses); + Log.debugf("Implementors: %s", classes); + return new ArrayList<>(classes); + } +} \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index a750a99..d7ced97 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -2,27 +2,30 @@ include::./includes/attributes.adoc[] -Easily create effective cross-browsers e2e tests for your Quarkus web-app using Playwright (Qute, Quinoa, Renarde, Web-Bundler, ...): +https://playwright.dev/[Playwright] is an open-source automation library designed for browser testing and web scraping. This extension supports two primary use cases: -== Installation +1. **Testing:** Perform end-to-end tests for your Quarkus web application. +2. **Runtime:** Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation. -If you want to use this extension, you need to add the `io.quarkiverse.playwright:quarkus-playwright` extension first to your build file. +== Test Usage -For instance, with Maven, add the following dependency to your POM file: +The primary use case for Playwright is integration with `@QuarkusTest` for end-to-end testing of your application. You can easily create effective cross-browser end-to-end tests for your Quarkus web application using Playwright with frameworks such as Qute, Quinoa, Renarde, Web-Bundler, and MyFaces. Playwright Test was specifically designed to meet the requirements of end-to-end testing. It supports all modern rendering engines, including Chromium, WebKit, and Firefox. You can run tests on Windows, Linux, and macOS—either locally or in CI—both in headless and headed modes, with native mobile emulation for Google Chrome on Android and Mobile Safari. + +Just add the `test` dependency to `pom.xml`: [source,xml,subs=attributes+] ---- io.quarkiverse.playwright - quarkus-playwright + quarkus-playwright-test {project-version} test ---- -== Usage +Write your tests: -[source,java] +[source, java] ---- @QuarkusTest @WithPlaywright @@ -32,6 +35,7 @@ public class WithDefaultPlaywrightTest { BrowserContext context; @TestHTTPResource("/") + URL index; @Test @@ -53,7 +57,43 @@ public class WithDefaultPlaywrightTest { } ---- -Use the annotation `@WithPlaywright()` to change the browser (Chromium, Firefox, Webkit), headless, enable debug, logs and other options. +Use the annotation `@WithPlaywright()` to change the browser (Chromium, Firefox, Webkit), headless, enable debug, logs, and other options. + +Debug your tests with the Playwright inspector `@WithPlaywright(debug=true)`. + +== Runtime Usage + +Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation. + +Just add the `runtime` dependency to `pom.xml`: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.playwright + quarkus-playwright + {project-version} + +---- + +== Native + +If you plan on running in a Docker image we highly recommend you use a pre-built image from Microsoft `mcr.microsoft.com/playwright:v1.48.1` which is based on Ubuntu and already has all libraries and tools necessary for PlayWright. +[source] +---- +FROM mcr.microsoft.com/playwright:v1.48.1-noble +WORKDIR /work/ +RUN chown 1001:root /work \ + && chmod g+rwX /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*.properties target/*.so /work/ +COPY --chown=1001:root target/*-runner /work/application +# Make application executable for all users +RUN chmod ugo+x /work/application +EXPOSE 8080 +USER 1001 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] +---- -include::includes/quarkus-playwright.adoc[leveloffset=+1, opts=optional] +include::includes/quarkus-playwright.adoc[leveloffset=+1, opts=optional] \ No newline at end of file diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 9a60037..a06f3fd 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -51,9 +51,21 @@ rest-assured test - + + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + docker + ${project.basedir} + + + + io.quarkus @@ -122,5 +134,49 @@ native + + native-docker + + + native-docker + + + + true + true + + --trace-object-instantiation=java.awt.BasicStroke + + + + clean package + + + org.codehaus.mojo + exec-maven-plugin + + + + docker-build + package + + exec + + + + build + -f + src/main/docker/Dockerfile.native + -t + playwright/integration-test:${project.version} + . + + + + + + + +
\ No newline at end of file diff --git a/integration-tests/src/main/docker/Dockerfile.native b/integration-tests/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..0476fd7 --- /dev/null +++ b/integration-tests/src/main/docker/Dockerfile.native @@ -0,0 +1,28 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./mvnw package -Pnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t playwright/integration-test . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 playwright/integration-test +# +### +FROM mcr.microsoft.com/playwright:v1.48.1-noble +WORKDIR /work/ +RUN chown 1001:root /work \ + && chmod g+rwX /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*.properties target/*.so /work/ +COPY --chown=1001:root target/*-runner /work/application +# Make application executable for all users +RUN chmod ugo+x /work/application +EXPOSE 8080 +USER 1001 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java b/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java index b6264eb..f7ee45a 100644 --- a/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java +++ b/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java @@ -24,6 +24,8 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import org.jboss.logging.Logger; + import com.microsoft.playwright.Browser; import com.microsoft.playwright.BrowserType; import com.microsoft.playwright.Page; @@ -32,24 +34,27 @@ @Path("/playwright") @ApplicationScoped public class PlaywrightResource { - // add some rest methods here + + private static final Logger log = Logger.getLogger(PlaywrightResource.class); @GET public String google() { - String pageTitle = "Hello playwright"; + String pageTitle; final BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions() .setHeadless(true) .setChromiumSandbox(false) .setChannel("") .setArgs(List.of("--disable-gpu")); final Map env = new HashMap<>(System.getenv()); + env.put("DEBUG", "pw:api"); try (Playwright playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env))) { try (Browser browser = playwright.chromium().launch(launchOptions)) { Page page = browser.newPage(); page.navigate("https://www.google.com/"); pageTitle = page.title(); + log.infof("Page title: %s", pageTitle); } } return pageTitle; } -} \ No newline at end of file +} diff --git a/runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java b/runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java new file mode 100644 index 0000000..c0570f5 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java @@ -0,0 +1,62 @@ +package io.quarkiverse.playwright.runtime; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.util.Collections; + +import org.jboss.logging.Logger; + +import com.microsoft.playwright.impl.driver.jar.DriverJar; + +import io.quarkus.runtime.annotations.Recorder; + +/** + * A recorder for managing Playwright driver initialization. + * This class handles the setup of the Playwright driver by + * loading the driver resources and creating a file system + * for those resources. + */ +@Recorder +public class PlaywrightRecorder { + + private static final Logger log = Logger.getLogger(PlaywrightRecorder.class); + + /** + * Initializes the Playwright driver by obtaining its resource URI, + * setting up a file system, and logging relevant information. + * + *

+ * This method attempts to retrieve the URI of the driver resources + * and creates a new file system for accessing those resources. If the + * file system cannot be created, an error is logged. + *

+ * + * In Native mode this is `resource:/` and must be registered before anything else. + * + * @throws RuntimeException if there is an error in URI syntax or + * during file system creation. + */ + public void initialize() { + try { + // Retrieve the URI of the Playwright driver resources + URI uri = DriverJar.getDriverResourceURI(); + log.infof("Playwright Driver: %s", uri); + + // Create a new file system for the driver resources typically resource:/ + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + if (fs == null) { + log.errorf("FileSystem Error NULL: %s", uri); + } + + // Log the directory where the driver is located + DriverJar jar = new DriverJar(); + log.debugf("Playwright Driver Directory: %s", jar.driverDir()); + } catch (URISyntaxException | IOException e) { + // Wrap and throw any exceptions that occur during initialization + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 95f5c46..3378317 100644 --- a/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,5 +1,5 @@ name: Playwright -description: Playwright enables reliable end-to-end testing for modern web apps. +description: Playwright is an open-source automation library for browser testing and web scraping. metadata: type: test keywords: @@ -7,8 +7,12 @@ metadata: - e2e - end-to-end - playwright + - browser + - screen-scraper guide: https://quarkiverse.github.io/quarkiverse-docs/playwright/dev/ icon-url: "https://playwright.dev/java/img/playwright-logo.svg" categories: - "testing" + config: + - "quarkus.playwright." status: "stable" \ No newline at end of file diff --git a/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java b/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java index c9eb49e..7865932 100644 --- a/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java +++ b/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java @@ -5,7 +5,33 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +/** + * Annotation to mark a field for injection of a Playwright instance. + *

+ * This annotation is used in Quarkus applications to automatically inject + * Playwright resources into fields where it is applied. It is intended + * for use with fields of classes that depend on Playwright for browser automation + * and testing. + *

+ *

+ * Usage example: + * + *

+ * {@code
+ * @InjectPlaywright
+ * private Playwright playwright;
+ * }
+ * 
+ *

+ * + *

+ * The annotation should be retained at runtime, as it is processed by the + * Quarkus framework to inject the necessary Playwright instances. + *

+ * + * @since 1.0 + */ +@Retention(RetentionPolicy.RUNTIME) // The annotation is available at runtime for Quarkus to process. +@Target(ElementType.FIELD) // This annotation can only be applied to fields. public @interface InjectPlaywright { -} +} \ No newline at end of file diff --git a/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java b/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java index d60ddd8..f86d957 100644 --- a/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java +++ b/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java @@ -16,35 +16,85 @@ import io.quarkus.test.common.QuarkusTestResourceConfigurableLifecycleManager; +/** + * Manages the lifecycle of a Playwright instance in Quarkus tests. + *

+ * This class implements {@link QuarkusTestResourceConfigurableLifecycleManager} and is responsible + * for initializing and configuring Playwright resources, such as the {@link Browser}, + * {@link BrowserContext}, and {@link Playwright} instance, according to the settings specified + * in the {@link WithPlaywright} annotation. + *

+ * + *

+ * This manager supports injecting Playwright resources into test classes marked with the + * {@code @InjectPlaywright} annotation, allowing easy access to {@link BrowserContext}, + * {@link Browser}, or {@link Playwright} instances. + *

+ * + * @see WithPlaywright + * @see InjectPlaywright + * @see QuarkusTestResourceConfigurableLifecycleManager + * @since 1.0 + */ public class QuarkusPlaywrightManager implements QuarkusTestResourceConfigurableLifecycleManager { + + /** Holds the configuration options from the {@link WithPlaywright} annotation. */ private WithPlaywright options; + /** The global Playwright instance for managing browser creation and operations. */ private Playwright playwright; + /** The context in which the browser operates, encapsulating tabs, storage, etc. */ private BrowserContext playwrightContext; + /** The specific browser instance (Chromium, Firefox, WebKit) launched by Playwright. */ private Browser playwrightBrowser; + /** + * Initializes the Playwright manager with configuration from {@link WithPlaywright}. + * + * @param withPlaywright the Playwright configuration options + */ @Override public void init(WithPlaywright withPlaywright) { this.options = withPlaywright; } + /** + * Not used in this implementation. Throws an exception to ensure only {@code @WithPlaywright} + * annotation is used for initialization. + * + * @param initArgs ignored initialization arguments + * @throws IllegalStateException always, as this method is unsupported + */ @Override public void init(Map initArgs) { throw new IllegalStateException("Use @WithPlaywright() annotation instead"); } + /** + * Starts the Playwright environment and configures the browser and context based on + * {@link WithPlaywright} options. + * + * @return an empty map as no additional environment variables are required + */ @Override public Map start() { final Map env = new HashMap<>(System.getenv()); + + // Enable Playwright verbose logging if requested if (!env.containsKey("DEBUG") && this.options.verbose()) { env.put("DEBUG", "pw:api"); } + // Enable Playwright debug mode if specified if (!env.containsKey("PWDEBUG") && this.options.debug()) { env.put("PWDEBUG", "1"); } + + // Create Playwright instance with the specified environment variables this.playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env)); + + // Configure launch options based on @WithPlaywright attributes final LaunchOptions launchOptions = new LaunchOptions() .setChannel(this.options.channel()) .setChromiumSandbox(this.options.chromiumSandbox()) @@ -52,17 +102,27 @@ public Map start() { .setSlowMo(this.options.slowMo()) .setEnv(env) .setArgs(Arrays.asList(this.options.args())); - this.playwrightBrowser = browser(playwright, this.options.browser()).launch( - launchOptions); + // Launch the browser based on the specified type (CHROMIUM, FIREFOX, or WEBKIT) + this.playwrightBrowser = browser(playwright, this.options.browser()).launch(launchOptions); + + // Configure the context, setting the video directory if specified final Browser.NewContextOptions contextOptions = new Browser.NewContextOptions(); if (StringUtils.isNotBlank(this.options.recordVideoDir())) { contextOptions.setRecordVideoDir(Paths.get(this.options.recordVideoDir())); } this.playwrightContext = playwrightBrowser.newContext(contextOptions); + return Collections.emptyMap(); } + /** + * Helper method to retrieve the correct {@link BrowserType} based on the specified browser. + * + * @param playwright the Playwright instance + * @param browser the browser type from the {@link WithPlaywright.Browser} enum + * @return the corresponding {@link BrowserType} instance + */ private static BrowserType browser(Playwright playwright, WithPlaywright.Browser browser) { return switch (browser) { case FIREFOX -> playwright.firefox(); @@ -71,26 +131,42 @@ private static BrowserType browser(Playwright playwright, WithPlaywright.Browser }; } + /** + * Closes and cleans up the Playwright resources, ensuring proper shutdown. + */ @Override public void stop() { if (this.playwrightContext != null) { - this.playwrightContext.close(); + this.playwrightContext.close(); // Closes the browser context this.playwrightContext = null; } if (playwright != null) { - playwright.close(); + playwright.close(); // Closes the Playwright instance playwright = null; } } + /** + * Injects Playwright resources into fields annotated with {@code @InjectPlaywright} in test classes. + *

+ * This method supports injection of {@link BrowserContext}, {@link Browser}, and {@link Playwright} + * into appropriately annotated fields. + *

+ * + * @param testInjector the test injector responsible for dependency injection + */ @Override public void inject(TestInjector testInjector) { + // Injects BrowserContext if @InjectPlaywright is present on a matching field testInjector.injectIntoFields(playwrightContext, new TestInjector.AnnotatedAndMatchesType(InjectPlaywright.class, BrowserContext.class)); + + // Injects Playwright if @InjectPlaywright is present on a matching field testInjector.injectIntoFields(playwright, new TestInjector.AnnotatedAndMatchesType(InjectPlaywright.class, Playwright.class)); + + // Injects Browser if @InjectPlaywright is present on a matching field testInjector.injectIntoFields(playwrightBrowser, new TestInjector.AnnotatedAndMatchesType(InjectPlaywright.class, Browser.class)); } - } \ No newline at end of file diff --git a/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java b/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java index fc66eba..c9fbd24 100644 --- a/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java +++ b/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java @@ -7,65 +7,123 @@ import io.quarkus.test.common.QuarkusTestResource; +/** + * Annotation to configure and enable Playwright for Quarkus tests. + *

+ * This annotation sets up a Playwright testing environment with options for + * browser selection, debugging, and configuration of other Playwright features. + * It is applied at the class level and managed by {@link QuarkusPlaywrightManager}. + *

+ * + *

+ * Usage example: + * + *

+ * {@code
+ * @WithPlaywright(browser = Browser.FIREFOX, headless = false, verbose = true)
+ * public class PlaywrightTest {
+ *     // Test code here
+ * }
+ * }
+ * 
+ *

+ * + * @see io.quarkus.test.common.QuarkusTestResource + * @see QuarkusPlaywrightManager + * @since 1.0 + */ @QuarkusTestResource(QuarkusPlaywrightManager.class) -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) // Annotation is retained at runtime for test setup. +@Target(ElementType.TYPE) // Applied only at the class level. public @interface WithPlaywright { /** - * Browser to use + * Specifies the browser to use for Playwright tests. + *

+ * Defaults to {@link Browser#CHROMIUM}. + *

*/ Browser browser() default Browser.CHROMIUM; /** - * Enable playwright logs + * Enables Playwright verbose logging. + *

+ * Set to {@code true} to enable detailed logging, useful for debugging. + *

*/ boolean verbose() default false; /** - * Enable Playwright Debug Inspector + * Enables the Playwright Debug Inspector for debugging tests. + *

+ * Use {@code true} to launch the inspector, which pauses tests for interactive debugging. + *

*/ boolean debug() default false; /** - * Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", - * "msedge-beta", "msedge-dev", "msedge-canary". Read more about using Google Chrome and Microsoft Edge. + * Specifies the distribution channel of the browser to use, such as "chrome" or "msedge". + *

+ * Supported values include "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", + * "msedge-beta", "msedge-dev", and "msedge-canary". + *

+ *

+ * Refer to the + * Playwright documentation for additional details on using these channels. + *

*/ String channel() default ""; /** - * Enable Chromium sandboxing. Defaults to {@code false}. + * Enables sandboxing for Chromium-based browsers. + *

+ * Defaults to {@code false} for compatibility. Set to {@code true} to enable sandboxing if supported. + *

*/ boolean chromiumSandbox() default false; /** - * Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to {@code true} - * unless the {@code devtools} option is {@code true}. + * Runs the browser in headless mode, which is suitable for CI environments. + *

+ * Defaults to {@code true} unless the {@code devtools} option is enabled. + *

+ *

+ * See more about headless mode in + * Chromium and Firefox. + *

*/ boolean headless() default true; /** - * Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. + * Slows down Playwright operations by the specified number of milliseconds. + *

+ * This is useful for observing browser interactions more clearly during tests. + *

*/ double slowMo() default 0; /** - * Enables video recording for all pages into the specified directory. If not specified videos are not recorded. + * Specifies the directory to store video recordings of all pages. + *

+ * If not set, video recording is disabled. + *

*/ String recordVideoDir() default ""; /** - * Args for use to launch the browser + * Specifies command-line arguments to use when launching the browser. + *

+ * Defaults to disabling GPU with {@code "--disable-gpu"}. + *

*/ String[] args() default { "--disable-gpu" }; + /** + * Enum representing the supported browsers for Playwright testing. + */ enum Browser { - CHROMIUM, - FIREFOX, - WEBKIT + CHROMIUM, // Google Chrome and other Chromium-based browsers. + FIREFOX, // Mozilla Firefox browser. + WEBKIT // WebKit browser, primarily for Safari compatibility. } - } \ No newline at end of file From 4f8c1b51905bd95db020b9d34386b8680fb8e727 Mon Sep 17 00:00:00 2001 From: melloware Date: Sun, 27 Oct 2024 10:41:55 -0400 Subject: [PATCH 3/6] fix poms --- deployment/pom.xml | 1 + pom.xml | 2 +- runtime/pom.xml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deployment/pom.xml b/deployment/pom.xml index 218fd35..11ae76d 100644 --- a/deployment/pom.xml +++ b/deployment/pom.xml @@ -30,6 +30,7 @@ + org.apache.maven.plugins maven-compiler-plugin diff --git a/pom.xml b/pom.xml index 9166a4f..e0c2190 100644 --- a/pom.xml +++ b/pom.xml @@ -66,8 +66,8 @@ ${quarkus.version} + org.apache.maven.plugins maven-compiler-plugin - ${compiler-plugin.version} true diff --git a/runtime/pom.xml b/runtime/pom.xml index ffffcec..cd25e7b 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -44,6 +44,7 @@ + org.apache.maven.plugins maven-compiler-plugin From 2752323738871fa131ac89e51aa191e7b3c50257 Mon Sep 17 00:00:00 2001 From: melloware Date: Mon, 28 Oct 2024 09:11:39 -0400 Subject: [PATCH 4/6] Refactor to no longer have Test jar --- README.md | 4 +-- .../deployment/PlaywrightProcessor.java | 2 +- docs/modules/ROOT/pages/index.adoc | 4 +-- integration-tests/pom.xml | 11 ++++---- .../org/acme/WithDefaultPlaywrightTest.java | 2 +- .../org/acme/WithFirefoxPlaywrightTest.java | 2 +- pom.xml | 3 +-- runtime/pom.xml | 11 +++++++- .../playwright/InjectPlaywright.java | 0 .../{runtime => }/PlaywrightRecorder.java | 2 +- .../playwright/QuarkusPlaywrightManager.java | 0 .../playwright/WithPlaywright.java | 0 testing/pom.xml | 25 ------------------- 13 files changed, 24 insertions(+), 42 deletions(-) rename {testing => runtime}/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java (100%) rename runtime/src/main/java/io/quarkiverse/playwright/{runtime => }/PlaywrightRecorder.java (98%) rename {testing => runtime}/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java (100%) rename {testing => runtime}/src/main/java/io/quarkiverse/playwright/WithPlaywright.java (100%) delete mode 100644 testing/pom.xml diff --git a/README.md b/README.md index 87f58b1..d790cf0 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ All the information you need to use Quarkus Playwright is in the [user documenta The primary use case for Playwright is integration with `@QuarkusTest` for end-to-end testing of your application. You can easily create effective cross-browser end-to-end tests for your Quarkus web application using Playwright with frameworks such as Qute, Quinoa, Renarde, Web-Bundler, and MyFaces. Playwright Test was specifically designed to meet the requirements of end-to-end testing. It supports all modern rendering engines, including Chromium, WebKit, and Firefox. You can run tests on Windows, Linux, and macOS—either locally or in CI—both in headless and headed modes, with native mobile emulation for Google Chrome on Android and Mobile Safari. -Just add the `test` dependency to pom.xml: +Just add the dependency as `test` to pom.xml: ```xml io.quarkiverse.playwright - quarkus-playwright-test + quarkus-playwright ${playwright.version} test diff --git a/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java index c88ba00..fc54ecd 100644 --- a/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java @@ -18,7 +18,7 @@ import com.microsoft.playwright.options.Timing; import com.microsoft.playwright.options.ViewportSize; -import io.quarkiverse.playwright.runtime.PlaywrightRecorder; +import io.quarkiverse.playwright.PlaywrightRecorder; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index d7ced97..a858fea 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -11,13 +11,13 @@ https://playwright.dev/[Playwright] is an open-source automation library designe The primary use case for Playwright is integration with `@QuarkusTest` for end-to-end testing of your application. You can easily create effective cross-browser end-to-end tests for your Quarkus web application using Playwright with frameworks such as Qute, Quinoa, Renarde, Web-Bundler, and MyFaces. Playwright Test was specifically designed to meet the requirements of end-to-end testing. It supports all modern rendering engines, including Chromium, WebKit, and Firefox. You can run tests on Windows, Linux, and macOS—either locally or in CI—both in headless and headed modes, with native mobile emulation for Google Chrome on Android and Mobile Safari. -Just add the `test` dependency to `pom.xml`: +Just add the dependency as `test` to your `pom.xml`: [source,xml,subs=attributes+] ---- io.quarkiverse.playwright - quarkus-playwright-test + quarkus-playwright {project-version} test diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index a06f3fd..4f8c93d 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -40,17 +40,16 @@ 3.7.1 provided - - io.quarkiverse.playwright - quarkus-playwright-test - ${project.version} - test - io.rest-assured rest-assured test + + io.quarkus + quarkus-junit5 + true + diff --git a/integration-tests/src/test/java/org/acme/WithDefaultPlaywrightTest.java b/integration-tests/src/test/java/org/acme/WithDefaultPlaywrightTest.java index 21eecd2..73b5345 100644 --- a/integration-tests/src/test/java/org/acme/WithDefaultPlaywrightTest.java +++ b/integration-tests/src/test/java/org/acme/WithDefaultPlaywrightTest.java @@ -42,4 +42,4 @@ public void testIndex() { Assertions.assertEquals("Hello from RESTEasy Reactive", greeting); } -} +} \ No newline at end of file diff --git a/integration-tests/src/test/java/org/acme/WithFirefoxPlaywrightTest.java b/integration-tests/src/test/java/org/acme/WithFirefoxPlaywrightTest.java index 9b059e7..2812983 100644 --- a/integration-tests/src/test/java/org/acme/WithFirefoxPlaywrightTest.java +++ b/integration-tests/src/test/java/org/acme/WithFirefoxPlaywrightTest.java @@ -44,4 +44,4 @@ public void testIndex() { Assertions.assertEquals("Hello from RESTEasy Reactive", greeting); } -} +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index e0c2190..9016db9 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,6 @@ deployment runtime - testing scm:git:git@github.com:quarkiverse/quarkus-playwright.git @@ -106,4 +105,4 @@ - + \ No newline at end of file diff --git a/runtime/pom.xml b/runtime/pom.xml index cd25e7b..de53efa 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -23,6 +23,15 @@ com.microsoft.playwright driver-bundle + + org.apache.commons + commons-lang3 + + + io.quarkus + quarkus-junit5 + true + @@ -63,4 +72,4 @@ - + \ No newline at end of file diff --git a/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java b/runtime/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java similarity index 100% rename from testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java rename to runtime/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java diff --git a/runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java b/runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java similarity index 98% rename from runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java rename to runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java index c0570f5..59560f3 100644 --- a/runtime/src/main/java/io/quarkiverse/playwright/runtime/PlaywrightRecorder.java +++ b/runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java @@ -1,4 +1,4 @@ -package io.quarkiverse.playwright.runtime; +package io.quarkiverse.playwright; import java.io.IOException; import java.net.URI; diff --git a/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java b/runtime/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java similarity index 100% rename from testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java rename to runtime/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java diff --git a/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java b/runtime/src/main/java/io/quarkiverse/playwright/WithPlaywright.java similarity index 100% rename from testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java rename to runtime/src/main/java/io/quarkiverse/playwright/WithPlaywright.java diff --git a/testing/pom.xml b/testing/pom.xml deleted file mode 100644 index 973dcff..0000000 --- a/testing/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - io.quarkiverse.playwright - quarkus-playwright-parent - 999-SNAPSHOT - - quarkus-playwright-test - Quarkus Playwright - Testing - - - com.microsoft.playwright - playwright - - - com.microsoft.playwright - driver-bundle - - - io.quarkus - quarkus-junit5 - - - From 54a17fe3741094dd99185348c70ea6c51f0c0134 Mon Sep 17 00:00:00 2001 From: melloware Date: Mon, 28 Oct 2024 11:01:43 -0400 Subject: [PATCH 5/6] Replace System.out with Jboss logging --- README.md | 5 +++ docs/modules/ROOT/pages/index.adoc | 5 +++ .../graal/DriverLoggingSubstitution.java | 32 +++++++++++++++ .../graal/LoggingSupportSubstitution.java | 41 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 runtime/src/main/java/io/quarkiverse/playwright/graal/DriverLoggingSubstitution.java create mode 100644 runtime/src/main/java/io/quarkiverse/playwright/graal/LoggingSupportSubstitution.java diff --git a/README.md b/README.md index d790cf0..0db8276 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ Just add the dependency as `test` to pom.xml: ${playwright.version} test + + io.quarkus + quarkus-junit5 + test + ``` Write your tests: ````java diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index a858fea..fb4ce45 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -21,6 +21,11 @@ Just add the dependency as `test` to your `pom.xml`: {project-version} test + + io.quarkus + quarkus-junit5 + test + ---- Write your tests: diff --git a/runtime/src/main/java/io/quarkiverse/playwright/graal/DriverLoggingSubstitution.java b/runtime/src/main/java/io/quarkiverse/playwright/graal/DriverLoggingSubstitution.java new file mode 100644 index 0000000..4d0ca19 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/graal/DriverLoggingSubstitution.java @@ -0,0 +1,32 @@ +package io.quarkiverse.playwright.graal; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.logging.Log; + +/** + * Replace System.err.println with Jboss Logging. + */ +@TargetClass(className = "com.microsoft.playwright.impl.driver.DriverLogging") +final class DriverLoggingSubstitution { + + @Alias + private static boolean isEnabled; + + @Alias + private static DateTimeFormatter timestampFormat; + + @Substitute + static void logWithTimestamp(String message) { + if (!isEnabled) { + return; + } + String timestamp = ZonedDateTime.now().format(timestampFormat); + Log.infof("%s %s", timestamp, message); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/playwright/graal/LoggingSupportSubstitution.java b/runtime/src/main/java/io/quarkiverse/playwright/graal/LoggingSupportSubstitution.java new file mode 100644 index 0000000..253215f --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/graal/LoggingSupportSubstitution.java @@ -0,0 +1,41 @@ +package io.quarkiverse.playwright.graal; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.logging.Log; + +/** + * Replace System.out.println with Jboss Logging. + */ +@TargetClass(className = "com.microsoft.playwright.impl.LoggingSupport") +final class LoggingSupportSubstitution { + + @Alias + private static boolean isEnabled; + + @Alias + private static DateTimeFormatter timestampFormat; + + @Substitute + static void logWithTimestamp(String message) { + String timestamp = ZonedDateTime.now().format(timestampFormat); + Log.infof("%s %s", timestamp, message); + } + + @Substitute + static void logApiIfEnabled(String message) { + if (isEnabled) { + logApi(message); + } + } + + @Substitute + static void logApi(String message) { + logWithTimestamp("pw:api " + message); + } +} From c0d8553de99d662ff4f1eae310aaa70970437b64 Mon Sep 17 00:00:00 2001 From: melloware Date: Mon, 28 Oct 2024 11:16:38 -0400 Subject: [PATCH 6/6] Change category to web --- .../src/main/resources/META-INF/quarkus-extension.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 3378317..bbf526b 100644 --- a/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,5 +1,5 @@ name: Playwright -description: Playwright is an open-source automation library for browser testing and web scraping. +description: Playwright is an open-source automation library for end-to-end browser testing and web scraping. metadata: type: test keywords: @@ -9,10 +9,10 @@ metadata: - playwright - browser - screen-scraper - guide: https://quarkiverse.github.io/quarkiverse-docs/playwright/dev/ - icon-url: "https://playwright.dev/java/img/playwright-logo.svg" categories: - - "testing" + - "web" config: - "quarkus.playwright." + guide: https://quarkiverse.github.io/quarkiverse-docs/playwright/dev/ + icon-url: "https://playwright.dev/java/img/playwright-logo.svg" status: "stable" \ No newline at end of file