Skip to content

Commit 1c80574

Browse files
committed
cherry-pick(#1938): feat(docker): pre-extract the driver in images to avoid /tmp unpacking
1 parent e8869de commit 1c80574

7 files changed

Lines changed: 112 additions & 17 deletions

File tree

driver/src/main/java/com/microsoft/playwright/impl/driver/Driver.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
public abstract class Driver {
3333
protected final Map<String, String> env = new LinkedHashMap<>(System.getenv());
3434
public static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
35+
public static final String PLAYWRIGHT_DRIVER_DIR = "PLAYWRIGHT_DRIVER_DIR";
3536

3637
private static Driver instance;
3738

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

110111
private static Driver newInstance() throws Exception {
111-
String pathFromProperty = System.getProperty("playwright.cli.dir");
112-
if (pathFromProperty != null) {
113-
return new PreinstalledDriver(Paths.get(pathFromProperty));
112+
String driverDir = System.getProperty("playwright.cli.dir");
113+
if (driverDir == null) {
114+
driverDir = System.getenv(PLAYWRIGHT_DRIVER_DIR);
115+
}
116+
if (driverDir != null) {
117+
return new PreinstalledDriver(Paths.get(driverDir));
114118
}
115119

116120
String driverImpl =

driver/src/main/java/com/microsoft/playwright/impl/driver/jar/DriverJar.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,11 @@ public class DriverJar extends Driver {
3030
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
3131
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
3232
private final Path driverTempDir;
33+
private final boolean deleteOnExit;
3334
private Path preinstalledNodePath;
3435

3536
public DriverJar() throws IOException {
36-
// Allow specifying custom path for the driver installation
37-
// See https://github.com/microsoft/playwright-java/issues/728
38-
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
39-
String prefix = "playwright-java-";
40-
driverTempDir = alternativeTmpdir == null
41-
? Files.createTempDirectory(prefix)
42-
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
43-
driverTempDir.toFile().deleteOnExit();
37+
this(createTempDriverDir(), true);
4438
String nodePath = System.getProperty("playwright.nodejs.path");
4539
if (nodePath != null) {
4640
preinstalledNodePath = Paths.get(nodePath);
@@ -51,6 +45,32 @@ public DriverJar() throws IOException {
5145
logMessage("created DriverJar: " + driverTempDir);
5246
}
5347

48+
private DriverJar(Path driverDir, boolean deleteOnExit) {
49+
this.driverTempDir = driverDir;
50+
this.deleteOnExit = deleteOnExit;
51+
if (deleteOnExit) {
52+
driverTempDir.toFile().deleteOnExit();
53+
}
54+
}
55+
56+
private static Path createTempDriverDir() throws IOException {
57+
// Allow specifying custom path for the driver installation
58+
// See https://github.com/microsoft/playwright-java/issues/728
59+
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
60+
String prefix = "playwright-java-";
61+
return alternativeTmpdir == null
62+
? Files.createTempDirectory(prefix)
63+
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
64+
}
65+
66+
// Extracts the driver (playwright-core package and the Node.js binary for the current platform)
67+
// into the given directory, persistently. Point playwright.cli.dir / PLAYWRIGHT_DRIVER_DIR at it
68+
// to run without extracting to a temp directory on every launch. See issue #1268.
69+
public static void installDriverTo(Path driverDir) throws IOException, URISyntaxException {
70+
Files.createDirectories(driverDir);
71+
new DriverJar(driverDir, false).extractDriverToTempDir();
72+
}
73+
5474
@Override
5575
protected void initialize(Boolean installBrowsers) throws Exception {
5676
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
@@ -156,7 +176,9 @@ private void extractResourceToDir(String resourcePath, Path destDir) throws URIS
156176
toPath.toFile().setExecutable(true, true);
157177
}
158178
}
159-
toPath.toFile().deleteOnExit();
179+
if (deleteOnExit) {
180+
toPath.toFile().deleteOnExit();
181+
}
160182
} catch (IOException e) {
161183
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
162184
}
@@ -179,7 +201,9 @@ private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
179201
Path fromPath = Paths.get(jarUri);
180202
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
181203
Files.copy(fromPath, toPath);
182-
toPath.toFile().deleteOnExit();
204+
if (deleteOnExit) {
205+
toPath.toFile().deleteOnExit();
206+
}
183207
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
184208
} catch (IOException e) {
185209
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);

playwright/src/main/java/com/microsoft/playwright/CLI.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
package com.microsoft.playwright;
1818

1919
import com.microsoft.playwright.impl.driver.Driver;
20+
import com.microsoft.playwright.impl.driver.jar.DriverJar;
2021

2122
import java.io.IOException;
23+
import java.net.URISyntaxException;
2224
import java.nio.file.Path;
25+
import java.nio.file.Paths;
2326
import java.util.Collections;
2427

2528
import static java.util.Arrays.asList;
@@ -28,7 +31,13 @@
2831
* Use this class to launch playwright cli.
2932
*/
3033
public class CLI {
31-
public static void main(String[] args) throws IOException, InterruptedException {
34+
public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException {
35+
// Extract the driver into a fixed directory instead of running the playwright CLI. This is
36+
// handled in Java because it must not require an already-extracted driver. See issue #1268.
37+
if (args.length > 0 && "install-driver".equals(args[0])) {
38+
installDriver(args);
39+
return;
40+
}
3241
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
3342
ProcessBuilder pb = driver.createProcessBuilder();
3443
pb.command().addAll(asList(args));
@@ -40,4 +49,17 @@ public static void main(String[] args) throws IOException, InterruptedException
4049
Process process = pb.start();
4150
System.exit(process.waitFor());
4251
}
52+
53+
private static void installDriver(String[] args) throws IOException, URISyntaxException {
54+
String dir = args.length > 1 ? args[1] : System.getenv(Driver.PLAYWRIGHT_DRIVER_DIR);
55+
if (dir == null) {
56+
System.err.println("Usage: install-driver <dir> (or set the " + Driver.PLAYWRIGHT_DRIVER_DIR
57+
+ " environment variable)");
58+
System.exit(1);
59+
return;
60+
}
61+
Path driverDir = Paths.get(dir);
62+
DriverJar.installDriverTo(driverDir);
63+
System.out.println("Installed Playwright driver into " + driverDir.toAbsolutePath());
64+
}
4365
}

playwright/src/test/java/com/microsoft/playwright/impl/driver/jar/TestInstall.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,30 @@ void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException,
132132
}
133133

134134

135+
@Test
136+
void canInstallDriverToDirectoryAndReuseIt(@TempDir Path tmpDir) throws Exception {
137+
Path driverDir = tmpDir.resolve("driver");
138+
DriverJar.installDriverTo(driverDir);
139+
// The directory is self-contained: the playwright-core package and the Node.js binary.
140+
assertTrue(Files.exists(driverDir.resolve("package").resolve("cli.js")));
141+
assertTrue(Files.exists(driverDir.resolve(isWindows() ? "node.exe" : "node")));
142+
143+
// Pointing playwright.cli.dir at it must reuse it as-is, without extracting to a temp directory.
144+
System.setProperty("playwright.cli.dir", driverDir.toString());
145+
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
146+
assertEquals(driverDir, driver.driverDir());
147+
148+
ProcessBuilder pb = driver.createProcessBuilder();
149+
pb.command().add("--version");
150+
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
151+
Path out = tmpDir.resolve("out.txt");
152+
pb.redirectOutput(out.toFile());
153+
Process p = pb.start();
154+
assertTrue(p.waitFor(1, TimeUnit.MINUTES), "Timed out waiting for version to be printed");
155+
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
156+
assertTrue(stdout.contains("Version "), stdout);
157+
}
158+
135159
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
136160
DriverJar auxDriver = new DriverJar();
137161
auxDriver.extractDriverToTempDir();

utils/docker/Dockerfile.jammy

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
3838

3939
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
4040

41+
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
42+
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
43+
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
44+
4145
RUN mkdir /ms-playwright && \
4246
mkdir /tmp/pw-java
4347

4448
COPY . /tmp/pw-java
4549

4650
RUN cd /tmp/pw-java && \
4751
mvn install -D skipTests --no-transfer-progress && \
52+
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
53+
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
4854
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
4955
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
5056
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
@@ -61,4 +67,5 @@ RUN cd /tmp/pw-java && \
6167
else \
6268
rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \
6369
fi && \
64-
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
70+
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
71+
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR

utils/docker/Dockerfile.noble

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,25 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
3838

3939
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
4040

41+
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
42+
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
43+
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
44+
4145
RUN mkdir /ms-playwright && \
4246
mkdir /tmp/pw-java
4347

4448
COPY . /tmp/pw-java
4549

4650
RUN cd /tmp/pw-java && \
4751
mvn install -D skipTests --no-transfer-progress && \
52+
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
53+
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
4854
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
4955
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
5056
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
5157
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
5258
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
5359
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
5460
rm -rf /tmp/pw-java && \
55-
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
61+
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
62+
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR

utils/docker/Dockerfile.resolute

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,25 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
3838

3939
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
4040

41+
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
42+
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
43+
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
44+
4145
RUN mkdir /ms-playwright && \
4246
mkdir /tmp/pw-java
4347

4448
COPY . /tmp/pw-java
4549

4650
RUN cd /tmp/pw-java && \
4751
mvn install -D skipTests --no-transfer-progress && \
52+
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
53+
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
4854
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
4955
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
5056
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
5157
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
5258
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
5359
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
5460
rm -rf /tmp/pw-java && \
55-
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
61+
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
62+
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR

0 commit comments

Comments
 (0)