Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Playwright in Runtime module and GraalVM support #95

Merged
merged 6 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@
[![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 `<scope>test</scope>` to pom.xml:
```xml
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>${playwright.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
```
Write your tests:
````java
Expand Down Expand Up @@ -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
<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 +133,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!
36 changes: 27 additions & 9 deletions deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>
<artifactId>quarkus-playwright-deployment</artifactId>
<name>Quarkus Playwright - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<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);
}
}
Original file line number Diff line number Diff line change
@@ -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<CardPageBuildItem> 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);
}
}
Loading