diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec550ed..2a3a214 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 + 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..0db8276 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,19 @@ [![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 dependency as `test` to pom.xml: ```xml io.quarkiverse.playwright @@ -23,6 +29,11 @@ Add to pom.xml: ${playwright.version} test + + io.quarkus + quarkus-junit5 + test + ``` Write your tests: ````java @@ -55,9 +66,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 +133,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/pom.xml b/deployment/pom.xml index 97697a1..11ae76d 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,43 @@ quarkus-playwright-deployment Quarkus Playwright - Deployment + + + io.quarkus + quarkus-arc-deployment + io.quarkiverse.playwright quarkus-playwright ${project.version} + + io.quarkus + quarkus-junit5-internal + test + + + org.apache.maven.plugins 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..fc54ecd --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/playwright/deployment/PlaywrightProcessor.java @@ -0,0 +1,139 @@ +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.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 { + + private static final String FEATURE = "playwright"; + + @BuildStep + 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/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/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index a750a99..fb4ce45 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -2,13 +2,16 @@ 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 dependency as `test` to your `pom.xml`: [source,xml,subs=attributes+] ---- @@ -18,11 +21,16 @@ For instance, with Maven, add the following dependency to your POM file: {project-version} test + + io.quarkus + quarkus-junit5 + test + ---- -== Usage +Write your tests: -[source,java] +[source, java] ---- @QuarkusTest @WithPlaywright @@ -32,6 +40,7 @@ public class WithDefaultPlaywrightTest { BrowserContext context; @TestHTTPResource("/") + URL index; @Test @@ -53,7 +62,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 ce975e4..4f8c93d 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 @@ -45,20 +40,31 @@ 3.7.1 provided - - io.quarkiverse.playwright - quarkus-playwright - ${project.version} - test - io.rest-assured rest-assured test - + + io.quarkus + quarkus-junit5 + true + + + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + docker + ${project.basedir} + + + + io.quarkus @@ -127,5 +133,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 new file mode 100644 index 0000000..f7ee45a --- /dev/null +++ b/integration-tests/src/main/java/io/quarkiverse/playwright/it/PlaywrightResource.java @@ -0,0 +1,60 @@ +/* +* 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 org.jboss.logging.Logger; + +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 { + + private static final Logger log = Logger.getLogger(PlaywrightResource.class); + + @GET + public String google() { + 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; + } +} 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/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 49ea96e..9016db9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,15 +11,15 @@ 999-SNAPSHOT pom Quarkus Playwright - Parent + Playwright enables reliable end-to-end testing for modern web apps - - testing + deployment + runtime 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 +38,22 @@ pom import + + com.microsoft.playwright + playwright + ${playright.version} + + + org.opentest4j + opentest4j + + + + + com.microsoft.playwright + driver-bundle + ${playright.version} + @@ -48,21 +64,18 @@ quarkus-maven-plugin ${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} + @@ -92,4 +105,4 @@ - + \ No newline at end of file diff --git a/runtime/pom.xml b/runtime/pom.xml new file mode 100644 index 0000000..de53efa --- /dev/null +++ b/runtime/pom.xml @@ -0,0 +1,75 @@ + + + 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 + + + org.apache.commons + commons-lang3 + + + io.quarkus + quarkus-junit5 + true + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + + \ No newline at end of file diff --git a/runtime/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java b/runtime/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java new file mode 100644 index 0000000..7865932 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java @@ -0,0 +1,37 @@ +package io.quarkiverse.playwright; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 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/runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java b/runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java new file mode 100644 index 0000000..59560f3 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/PlaywrightRecorder.java @@ -0,0 +1,62 @@ +package io.quarkiverse.playwright; + +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/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java b/runtime/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java new file mode 100644 index 0000000..f86d957 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java @@ -0,0 +1,172 @@ +package io.quarkiverse.playwright; + +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.BrowserType.LaunchOptions; +import com.microsoft.playwright.Playwright; + +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()) + .setHeadless(this.options.headless()) + .setSlowMo(this.options.slowMo()) + .setEnv(env) + .setArgs(Arrays.asList(this.options.args())); + + // 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(); + case WEBKIT -> playwright.webkit(); + default -> playwright.chromium(); + }; + } + + /** + * Closes and cleans up the Playwright resources, ensuring proper shutdown. + */ + @Override + public void stop() { + if (this.playwrightContext != null) { + this.playwrightContext.close(); // Closes the browser context + this.playwrightContext = null; + } + if (playwright != null) { + 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/runtime/src/main/java/io/quarkiverse/playwright/WithPlaywright.java b/runtime/src/main/java/io/quarkiverse/playwright/WithPlaywright.java new file mode 100644 index 0000000..c9fbd24 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/playwright/WithPlaywright.java @@ -0,0 +1,129 @@ +package io.quarkiverse.playwright; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +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) // Annotation is retained at runtime for test setup. +@Target(ElementType.TYPE) // Applied only at the class level. +public @interface WithPlaywright { + + /** + * Specifies the browser to use for Playwright tests. + *

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

+ */ + Browser browser() default Browser.CHROMIUM; + + /** + * Enables Playwright verbose logging. + *

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

+ */ + boolean verbose() default false; + + /** + * 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; + + /** + * 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 ""; + + /** + * 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; + + /** + * 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 number of milliseconds. + *

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

+ */ + double slowMo() default 0; + + /** + * Specifies the directory to store video recordings of all pages. + *

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

+ */ + String recordVideoDir() default ""; + + /** + * 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, // Google Chrome and other Chromium-based browsers. + FIREFOX, // Mozilla Firefox browser. + WEBKIT // WebKit browser, primarily for Safari compatibility. + } +} \ No newline at end of file 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); + } +} diff --git a/testing/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml similarity index 51% rename from testing/src/main/resources/META-INF/quarkus-extension.yaml rename to runtime/src/main/resources/META-INF/quarkus-extension.yaml index ec0bb49..bbf526b 100644 --- a/testing/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 end-to-end browser testing and web scraping. metadata: type: test keywords: @@ -7,8 +7,12 @@ 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 - icon-url: "https://playwright.dev/java/img/playwright-logo.svg" + - browser + - screen-scraper categories: - - "testing" - status: "stable" + - "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 diff --git a/testing/pom.xml b/testing/pom.xml deleted file mode 100644 index 919da3a..0000000 --- a/testing/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - 4.0.0 - - io.quarkiverse.playwright - quarkus-playwright-parent - 999-SNAPSHOT - - quarkus-playwright - Quarkus Playwright - Testing - - - com.microsoft.playwright - playwright - ${playright.version} - - - org.opentest4j - opentest4j - - - - - com.microsoft.playwright - driver-bundle - ${playright.version} - - - io.quarkus - quarkus-junit5 - - - diff --git a/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java b/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java deleted file mode 100644 index c9eb49e..0000000 --- a/testing/src/main/java/io/quarkiverse/playwright/InjectPlaywright.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.quarkiverse.playwright; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface InjectPlaywright { -} diff --git a/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java b/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java deleted file mode 100644 index d60ddd8..0000000 --- a/testing/src/main/java/io/quarkiverse/playwright/QuarkusPlaywrightManager.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.quarkiverse.playwright; - -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.microsoft.playwright.Browser; -import com.microsoft.playwright.BrowserContext; -import com.microsoft.playwright.BrowserType; -import com.microsoft.playwright.BrowserType.LaunchOptions; -import com.microsoft.playwright.Playwright; - -import io.quarkus.test.common.QuarkusTestResourceConfigurableLifecycleManager; - -public class QuarkusPlaywrightManager implements QuarkusTestResourceConfigurableLifecycleManager { - private WithPlaywright options; - - private Playwright playwright; - - private BrowserContext playwrightContext; - - private Browser playwrightBrowser; - - @Override - public void init(WithPlaywright withPlaywright) { - this.options = withPlaywright; - } - - @Override - public void init(Map initArgs) { - throw new IllegalStateException("Use @WithPlaywright() annotation instead"); - } - - @Override - public Map start() { - final Map env = new HashMap<>(System.getenv()); - if (!env.containsKey("DEBUG") && this.options.verbose()) { - env.put("DEBUG", "pw:api"); - } - if (!env.containsKey("PWDEBUG") && this.options.debug()) { - env.put("PWDEBUG", "1"); - } - this.playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env)); - final LaunchOptions launchOptions = new LaunchOptions() - .setChannel(this.options.channel()) - .setChromiumSandbox(this.options.chromiumSandbox()) - .setHeadless(this.options.headless()) - .setSlowMo(this.options.slowMo()) - .setEnv(env) - .setArgs(Arrays.asList(this.options.args())); - this.playwrightBrowser = browser(playwright, this.options.browser()).launch( - launchOptions); - - 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(); - } - - private static BrowserType browser(Playwright playwright, WithPlaywright.Browser browser) { - return switch (browser) { - case FIREFOX -> playwright.firefox(); - case WEBKIT -> playwright.webkit(); - default -> playwright.chromium(); - }; - } - - @Override - public void stop() { - if (this.playwrightContext != null) { - this.playwrightContext.close(); - this.playwrightContext = null; - } - if (playwright != null) { - playwright.close(); - playwright = null; - } - } - - @Override - public void inject(TestInjector testInjector) { - testInjector.injectIntoFields(playwrightContext, - new TestInjector.AnnotatedAndMatchesType(InjectPlaywright.class, BrowserContext.class)); - testInjector.injectIntoFields(playwright, - new TestInjector.AnnotatedAndMatchesType(InjectPlaywright.class, Playwright.class)); - 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 deleted file mode 100644 index fc66eba..0000000 --- a/testing/src/main/java/io/quarkiverse/playwright/WithPlaywright.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.quarkiverse.playwright; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import io.quarkus.test.common.QuarkusTestResource; - -@QuarkusTestResource(QuarkusPlaywrightManager.class) -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface WithPlaywright { - - /** - * Browser to use - */ - Browser browser() default Browser.CHROMIUM; - - /** - * Enable playwright logs - */ - boolean verbose() default false; - - /** - * Enable Playwright Debug Inspector - */ - 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. - */ - String channel() default ""; - - /** - * Enable Chromium sandboxing. Defaults to {@code false}. - */ - 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}. - */ - boolean headless() default true; - - /** - * Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. - */ - double slowMo() default 0; - - /** - * Enables video recording for all pages into the specified directory. If not specified videos are not recorded. - */ - String recordVideoDir() default ""; - - /** - * Args for use to launch the browser - */ - String[] args() default { "--disable-gpu" }; - - enum Browser { - CHROMIUM, - FIREFOX, - WEBKIT - } - -} \ No newline at end of file