Skip to content

Commit

Permalink
Native Build work
Browse files Browse the repository at this point in the history
  • Loading branch information
melloware committed Oct 26, 2024
1 parent 262c359 commit 15342cd
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 22 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
- name: Build with Maven (Native)
run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<artifactId>quarkus-playwright-test</artifactId>
<version>${playwright.version}</version>
<scope>test</scope>
</dependency>
Expand Down Expand Up @@ -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
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>${playwright.version}</version>
</dependency>
```

## 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)):
Expand Down Expand Up @@ -87,4 +128,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d

<!-- ALL-CONTRIBUTORS-LIST:END -->

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!
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -11,4 +40,100 @@ class PlaywrightProcessor {
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
}

@BuildStep
void indexTransitiveDependencies(BuildProducer<IndexDependencyBuildItem> 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<ReflectiveClassBuildItem> reflectiveClass) {
//@formatter:off
final List<String> 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<String> 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<NativeImageResourcePatternsBuildItem> nativeImageResourcePatterns) {
final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder();
builder.includeGlob("driver/**");
nativeImageResourcePatterns.produce(builder.build());
}

private List<String> collectSubclasses(CombinedIndexBuildItem combinedIndex, String className) {
List<String> 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<String> collectImplementors(CombinedIndexBuildItem combinedIndex, String className) {
Set<String> classes = combinedIndex.getIndex()
.getAllKnownImplementors(DotName.createSimple(className))
.stream()
.map(ClassInfo::toString)
.collect(Collectors.toCollection(HashSet::new));
classes.add(className);
Set<String> subclasses = new HashSet<>();
for (String implementationClass : classes) {
subclasses.addAll(collectSubclasses(combinedIndex, implementationClass));
}
classes.addAll(subclasses);
Log.debugf("Implementors: %s", classes);
return new ArrayList<>(classes);
}
}
58 changes: 49 additions & 9 deletions docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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+]
----
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<artifactId>quarkus-playwright-test</artifactId>
<version>{project-version}</version>
<scope>test</scope>
</dependency>
----

== Usage
Write your tests:

[source,java]
[source, java]
----
@QuarkusTest
@WithPlaywright
Expand All @@ -32,6 +35,7 @@ public class WithDefaultPlaywrightTest {
BrowserContext context;
@TestHTTPResource("/")
URL index;
@Test
Expand All @@ -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+]
----
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>{project-version}</version>
</dependency>
----

== 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]
58 changes: 57 additions & 1 deletion integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,21 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -122,5 +134,49 @@
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
<profile>
<id>native-docker</id>
<activation>
<property>
<name>native-docker</name>
</property>
</activation>
<properties>
<skipITs>true</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.native.additional-build-args>
--trace-object-instantiation=java.awt.BasicStroke
</quarkus.native.additional-build-args>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<!-- $ docker build -f src/main/docker/Dockerfile.native -t playwright/integration-test:version . -->
<execution>
<id>docker-build</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>build</argument>
<argument>-f</argument>
<argument>src/main/docker/Dockerfile.native</argument>
<argument>-t</argument>
<argument>playwright/integration-test:${project.version}</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Loading

0 comments on commit 15342cd

Please sign in to comment.