Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
public abstract class Driver {
protected final Map<String, String> env = new LinkedHashMap<>(System.getenv());
public static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
public static final String PLAYWRIGHT_DRIVER_DIR = "PLAYWRIGHT_DRIVER_DIR";

private static Driver instance;

Expand Down Expand Up @@ -108,9 +109,12 @@ public static Driver createAndInstall(Map<String, String> env, Boolean installBr
}

private static Driver newInstance() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
return new PreinstalledDriver(Paths.get(pathFromProperty));
String driverDir = System.getProperty("playwright.cli.dir");
if (driverDir == null) {
driverDir = System.getenv(PLAYWRIGHT_DRIVER_DIR);
}
if (driverDir != null) {
return new PreinstalledDriver(Paths.get(driverDir));
}

String driverImpl =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,11 @@ public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
private final Path driverTempDir;
private final boolean deleteOnExit;
private Path preinstalledNodePath;

public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
driverTempDir = alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
driverTempDir.toFile().deleteOnExit();
this(createTempDriverDir(), true);
String nodePath = System.getProperty("playwright.nodejs.path");
if (nodePath != null) {
preinstalledNodePath = Paths.get(nodePath);
Expand All @@ -51,6 +45,32 @@ public DriverJar() throws IOException {
logMessage("created DriverJar: " + driverTempDir);
}

private DriverJar(Path driverDir, boolean deleteOnExit) {
this.driverTempDir = driverDir;
this.deleteOnExit = deleteOnExit;
if (deleteOnExit) {
driverTempDir.toFile().deleteOnExit();
}
}

private static Path createTempDriverDir() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
return alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
}

// Extracts the driver (playwright-core package and the Node.js binary for the current platform)
// into the given directory, persistently. Point playwright.cli.dir / PLAYWRIGHT_DRIVER_DIR at it
// to run without extracting to a temp directory on every launch. See issue #1268.
public static void installDriverTo(Path driverDir) throws IOException, URISyntaxException {
Files.createDirectories(driverDir);
new DriverJar(driverDir, false).extractDriverToTempDir();
}

@Override
protected void initialize(Boolean installBrowsers) throws Exception {
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
Expand Down Expand Up @@ -156,7 +176,9 @@ private void extractResourceToDir(String resourcePath, Path destDir) throws URIS
toPath.toFile().setExecutable(true, true);
}
}
toPath.toFile().deleteOnExit();
if (deleteOnExit) {
toPath.toFile().deleteOnExit();
}
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
}
Expand All @@ -179,7 +201,9 @@ private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
Path fromPath = Paths.get(jarUri);
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
Files.copy(fromPath, toPath);
toPath.toFile().deleteOnExit();
if (deleteOnExit) {
toPath.toFile().deleteOnExit();
}
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
Expand Down
24 changes: 23 additions & 1 deletion playwright/src/main/java/com/microsoft/playwright/CLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
package com.microsoft.playwright;

import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.driver.jar.DriverJar;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;

import static java.util.Arrays.asList;
Expand All @@ -28,7 +31,13 @@
* Use this class to launch playwright cli.
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException {
// Extract the driver into a fixed directory instead of running the playwright CLI. This is
// handled in Java because it must not require an already-extracted driver. See issue #1268.
if (args.length > 0 && "install-driver".equals(args[0])) {
installDriver(args);
return;
}
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().addAll(asList(args));
Expand All @@ -40,4 +49,17 @@ public static void main(String[] args) throws IOException, InterruptedException
Process process = pb.start();
System.exit(process.waitFor());
}

private static void installDriver(String[] args) throws IOException, URISyntaxException {
String dir = args.length > 1 ? args[1] : System.getenv(Driver.PLAYWRIGHT_DRIVER_DIR);
if (dir == null) {
System.err.println("Usage: install-driver <dir> (or set the " + Driver.PLAYWRIGHT_DRIVER_DIR
+ " environment variable)");
System.exit(1);
return;
}
Path driverDir = Paths.get(dir);
DriverJar.installDriverTo(driverDir);
System.out.println("Installed Playwright driver into " + driverDir.toAbsolutePath());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException,
}


@Test
void canInstallDriverToDirectoryAndReuseIt(@TempDir Path tmpDir) throws Exception {
Path driverDir = tmpDir.resolve("driver");
DriverJar.installDriverTo(driverDir);
// The directory is self-contained: the playwright-core package and the Node.js binary.
assertTrue(Files.exists(driverDir.resolve("package").resolve("cli.js")));
assertTrue(Files.exists(driverDir.resolve(isWindows() ? "node.exe" : "node")));

// Pointing playwright.cli.dir at it must reuse it as-is, without extracting to a temp directory.
System.setProperty("playwright.cli.dir", driverDir.toString());
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
assertEquals(driverDir, driver.driverDir());

ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("--version");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Path out = tmpDir.resolve("out.txt");
pb.redirectOutput(out.toFile());
Process p = pb.start();
assertTrue(p.waitFor(1, TimeUnit.MINUTES), "Timed out waiting for version to be printed");
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
assertTrue(stdout.contains("Version "), stdout);
}

private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
DriverJar auxDriver = new DriverJar();
auxDriver.extractDriverToTempDir();
Expand Down
9 changes: 8 additions & 1 deletion utils/docker/Dockerfile.jammy
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}

ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver

RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
Expand All @@ -61,4 +67,5 @@ RUN cd /tmp/pw-java && \
else \
rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \
fi && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
9 changes: 8 additions & 1 deletion utils/docker/Dockerfile.noble
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,25 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}

ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver

RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
9 changes: 8 additions & 1 deletion utils/docker/Dockerfile.resolute
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,25 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}

ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver

RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
Loading