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

Update selenium tutorial #160

Merged
merged 19 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
69 changes: 53 additions & 16 deletions tutorials/selenium/README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
# JxBrowser and Selenium WebDriver

This tutorial demonstrates how to create a simple Selenium WebDriver application that is configured
to access a web page loaded in a desktop Java app using JxBrowser on Windows.
This tutorial demonstrates how to create a simple Selenium WebDriver application
that is configured to access a web page in a desktop Java application based on
JxBrowser.

Creating of these example projects is described in
the [tutorial](https://jxbrowser-support.teamdev.com/docs/tutorials/integration/selenium.html).

## Prerequisites

The tutorial requires the `jpackage` tool available in `PATH`, so make sure that
the environment variable contains the `%JAVA_HOME%/bin` directory.

## Building and Launching

1. Download [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads). Put it
into the `launcher/src/main/resources` directory. Use one of the latest versions.
2. [Insert your license key](https://jxbrowser-support.teamdev.com/docs/guides/licensing.html#adding-the-license-to-a-project)
into [the test application](https://github.com/TeamDev-IP/JxBrowser-Examples/blob/90fdd92f7c4c8737929f57e0383aad39d4be2aee/tutorials/selenium/target-app/src/main/java/TargetApp.java#L45)
.
3. In the [target-app](target-app) module run the `createExe` Gradle task.
4. Run [Selenium](launcher/src/main/java/SeleniumLauncher.java).
1. In the [root](../..) directory, run the `downloadChromeDriver` task of
the [launcher](launcher) module to download ChromeDriver to the
`resources` directory:

**Windows:**
```bash
gradlew launcher:downloadChromeDriver
vladimir-ikryanov marked this conversation as resolved.
Show resolved Hide resolved
```

**macOS/Linux:**
```bash
./gradlew launcher:downloadChromeDriver
```

_You may need to run Selenium twice as during the first start ChromeDriver may not detect the
running application binaries._
Each version of ChromeDriver corresponds to the Chromium version used in
JxBrowser. If you change the JxBrowser version
in [build.gradle.kts](../../build.gradle.kts) you need to update the
`chromiumVersion` property [here](launcher/build.gradle.kts)
before running the `downloadChromeDriver` task. Otherwise, Selenium WebDriver
will not be able to connect to Chromium. You can find which Chromium version
is used in the specific JxBrowser version in
the [release notes](https://teamdev.com/jxbrowser/release-notes/).
2. [Insert your license key](https://teamdev.com/jxbrowser/docs/guides/introduction/licensing/#adding-the-license-to-a-project)
into [the test application](./target-app/src/main/java/App.java).
3. In the [root](../..) directory, run the `buildApplication` task
of the [target-app](target-app) module to create an executable file for the
current platform that later will be run by Selenium WebDriver.

5. If everything configured properly, you will see the launched test application, and the following
in the console output:
**Windows:**
```bash
gradlew target-app:clean target-app:buildApplication
```

**macOS/Linux:**
```bash
./gradlew target-app:clean target-app:buildApplication
```

4. Run [Selenium](launcher/src/main/java/SeleniumLauncher.java).
5. If everything configured properly, you will see the launched test
application, and the following console output:
```
Current URL: https://www.google.com/
Current URL: about:blank
// or
Current URL: https://www.google.com/
```
It means that Selenium WebDriver managed to successfully run the application, set up a connection
with the JxBrowser's Chromium engine, and access the loaded web page to print its URL.
It means that Selenium WebDriver managed to successfully run the application,
set up a connection with the JxBrowser's Chromium, and access the loaded
web page to print its URL.
102 changes: 101 additions & 1 deletion tutorials/selenium/launcher/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import java.net.URL
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.nio.file.attribute.PosixFilePermission.*
import java.util.*
import java.util.zip.ZipFile

/*
* Copyright 2024, TeamDev. All rights reserved.
*
Expand All @@ -19,5 +26,98 @@
*/

dependencies {
implementation(group = "org.seleniumhq.selenium", name = "selenium-chrome-driver", version = "3.141.59")
implementation("org.seleniumhq.selenium:selenium-chrome-driver:4.26.0")
}

// The Chromium version used in JxBrowser 8.1.0.
val chromiumVersion = "130.0.6723.70"

fun chromeDriverPlatform(): String {
fun isArm(): Boolean {
val arch = System.getProperty("os.arch")
return "aarch64" == arch || "arm" == arch
}

val os = System.getProperty("os.name")
return when {
os.startsWith("Windows") -> {
"win64"
}

os.startsWith("Linux") -> {
"linux64"
}

os.startsWith("Mac") -> {
if (isArm()) {
"mac-arm64"
} else {
"mac-x64"
}
}

else -> {
throw IllegalStateException("Unsupported operating system.")
}
}
}

tasks.register("downloadChromeDriver") {
val chromeDriverPlatform = chromeDriverPlatform()
val downloadUrl =
"https://storage.googleapis.com/chrome-for-testing-public/$chromiumVersion/$chromeDriverPlatform/chromedriver-$chromeDriverPlatform.zip"
val resourcesDir = sourceSets["main"].resources.srcDirs.first()
if (!resourcesDir.exists()) {
mkdir(resourcesDir)
}
val chromeDriverZip = resourcesDir.resolve("chromedriver.zip")
val chromeDriver = resourcesDir.resolve("chromedriver")

doLast {
try {
URL(downloadUrl).openStream().use { inputStream ->
Files.copy(
inputStream,
chromeDriverZip.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
println("ChromeDriver downloaded to ${chromeDriverZip.absolutePath}")
ZipFile(chromeDriverZip).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (!entry.isDirectory) {
val outputFile =
File(resourcesDir, File(entry.name).name)
zip.getInputStream(entry).use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
val extension = outputFile.extension
if (extension.isEmpty() &&
!System.getProperty("os.name")
.startsWith("Windows")
) {
Files.setPosixFilePermissions(
outputFile.toPath(),
EnumSet.of(
OWNER_EXECUTE,
OWNER_READ,
OWNER_WRITE,
GROUP_EXECUTE,
GROUP_READ,
GROUP_WRITE
)
)
}
}
}
println("ChromeDriver extracted to ${chromeDriver.absolutePath}")
}
}
} finally {
if (chromeDriverZip.exists()) {
delete(chromeDriverZip)
}
}
}
}
48 changes: 39 additions & 9 deletions tutorials/selenium/launcher/src/main/java/SeleniumLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,69 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import static com.teamdev.jxbrowser.os.Environment.isLinux;
import static com.teamdev.jxbrowser.os.Environment.isMac;
import static com.teamdev.jxbrowser.os.Environment.isWindows;
import static java.util.Objects.requireNonNull;

import java.io.File;
import org.openqa.selenium.WebDriver;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

/**
* An application that configures Selenium WebDriver (ChromeDriver) to run on the JxBrowser-based
* application binaries and get access to HTML content loaded in JxBrowser.
* Configures Selenium WebDriver (ChromeDriver) to run the JxBrowser-based
* application and get access to the loaded web page.
*/
public final class SeleniumLauncher {

public static void main(String[] args) {
public static void main(String[] args) throws URISyntaxException {
URL chromeDriverUrl = requireNonNull(
SeleniumLauncher.class.getResource(chromeDriverFile()));
// Set a path to the ChromeDriver executable.
System.setProperty("webdriver.chrome.driver",
"tutorials/selenium/launcher/src/main/resources/chromedriver.exe");
Paths.get(chromeDriverUrl.toURI()).toString());

// #docfragment "path-to-exe"
ChromeOptions options = new ChromeOptions();
var options = new ChromeOptions();

// Set a path to your JxBrowser application executable.
options.setBinary(
new File("tutorials/selenium/target-app/build/executable/TargetApp.exe"));
options.setBinary(new File(binaryPath()));
// #enddocfragment "path-to-exe"
// #docfragment "set-remote-debugging-port"
// Set a port to communicate on.
options.addArguments("--remote-debugging-port=9222");
// #enddocfragment "set-remote-debugging-port"

WebDriver driver = new ChromeDriver(options);
var driver = new ChromeDriver(options);

// Now you can use WebDriver.
System.out.printf("Current URL: %s\n", driver.getCurrentUrl());

driver.quit();
}

private static String binaryPath() {
var applicationDirectory =
"tutorials/selenium/target-app/build/application/";
if (isMac()) {
return applicationDirectory
+ "App.app/Contents/MacOS/App";
} else if (isWindows()) {
return applicationDirectory + "App/App.exe";
} else if (isLinux()) {
return applicationDirectory + "App/bin/App";
}

throw new IllegalStateException("The platform is unsupported.");
}

private static String chromeDriverFile() {
if (isWindows()) {
return "chromedriver.exe";
}
return "chromedriver";
}
}
33 changes: 27 additions & 6 deletions tutorials/selenium/target-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,33 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

plugins {
id("edu.sc.seis.launch4j") version "3.0.4"
val mainJar = "app.jar"

tasks.jar {
archiveFileName.set(mainJar)
manifest {
attributes["Main-Class"] = "App"
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from({
configurations.runtimeClasspath.get().map {
if (it.isDirectory) it else zipTree(it)
}
})
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
}

launch4j {
outfile = "TargetApp.exe"
mainClassName = "TargetApp"
outputDir = "executable"
tasks.register<Exec>("buildApplication") {
dependsOn(tasks.build)

commandLine(
"jpackage",
"--input", "./build/libs",
"--main-jar", mainJar,
"--name", "App",
"--app-version", version,
"--type", "app-image",
"--main-class", "App",
"--dest", "./build/application",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;

import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.engine.EngineOptions;
import com.teamdev.jxbrowser.view.swing.BrowserView;
Expand All @@ -33,11 +32,11 @@
import javax.swing.WindowConstants;

/**
* This example demonstrates how to create a simple Swing application with a web page loaded in
* BrowserView, and connect JxBrowser's Chromium engine with Selenium via the remote debugging port
* obtained from the command line.
* A simple Swing application with a web page loaded in {@code BrowserView} that
* will be run by Selenium WebDriver later on and debugged via the remote
* debugging port obtained from the command line.
*/
public final class TargetApp {
public final class App {

private static final String REMOTE_DEBUGGING_PORT_ARG = "--remote-debugging-port=";

Expand All @@ -47,23 +46,24 @@ public static void main(String[] args) {

// #docfragment "forward-remote-debugging-port"
// Create a builder for EngineOptions.
EngineOptions.Builder builder = EngineOptions.newBuilder(HARDWARE_ACCELERATED);
var builder = EngineOptions.newBuilder(HARDWARE_ACCELERATED);

// Configure Engine with the remote debugging port obtained from the command line args.
remoteDebuggingPortFromCommandLine(args).ifPresent(builder::remoteDebuggingPort);
remoteDebuggingPortFromCommandLine(args).ifPresent(
builder::remoteDebuggingPort);
// #enddocfragment "forward-remote-debugging-port"

// Creating Chromium engine.
Engine engine = Engine.newInstance(builder.build());
Browser browser = engine.newBrowser();
var engine = Engine.newInstance(builder.build());
var browser = engine.newBrowser();

SwingUtilities.invokeLater(() -> {
// Creating Swing component for rendering web content
// loaded in the given Browser instance.
BrowserView view = BrowserView.newInstance(browser);
var view = BrowserView.newInstance(browser);

// Creating and displaying Swing app frame.
JFrame frame = new JFrame();
var frame = new JFrame();
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Expand All @@ -81,11 +81,13 @@ public void windowClosing(WindowEvent e) {
}

// #docfragment "get-remote-debugging-port"
private static Optional<Integer> remoteDebuggingPortFromCommandLine(String[] args) {
private static Optional<Integer> remoteDebuggingPortFromCommandLine(
String[] args) {
if (args.length > 0) {
for (String arg : args) {
for (var arg : args) {
if (arg.startsWith(REMOTE_DEBUGGING_PORT_ARG)) {
String port = arg.substring(REMOTE_DEBUGGING_PORT_ARG.length());
var port = arg.substring(
REMOTE_DEBUGGING_PORT_ARG.length());
return Optional.of(Integer.parseInt(port));
}
}
Expand Down
Loading