feat: allow using preinstalled node.js (#1030)
This commit is contained in:
+30
-6
@@ -29,7 +29,9 @@ import java.util.concurrent.TimeUnit;
|
||||
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";
|
||||
static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
|
||||
private final Path driverTempDir;
|
||||
private Path preinstalledNodePath;
|
||||
|
||||
public DriverJar() throws IOException {
|
||||
// Allow specifying custom path for the driver installation
|
||||
@@ -40,11 +42,27 @@ public class DriverJar extends Driver {
|
||||
? Files.createTempDirectory(prefix)
|
||||
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
String nodePath = System.getProperty("playwright.nodejs.path");
|
||||
if (nodePath != null) {
|
||||
preinstalledNodePath = Paths.get(nodePath);
|
||||
if (!Files.exists(preinstalledNodePath)) {
|
||||
throw new RuntimeException("Invalid Node.js path specified: " + nodePath);
|
||||
}
|
||||
}
|
||||
logMessage("created DriverJar: " + driverTempDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
protected void initialize(Boolean installBrowsers) throws Exception {
|
||||
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
|
||||
preinstalledNodePath = Paths.get(env.get(PLAYWRIGHT_NODEJS_PATH));
|
||||
if (!Files.exists(preinstalledNodePath)) {
|
||||
throw new RuntimeException("Invalid Node.js path specified: " + preinstalledNodePath);
|
||||
}
|
||||
} else if (preinstalledNodePath != null) {
|
||||
// Pass the env variable to the driver process.
|
||||
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
|
||||
}
|
||||
extractDriverToTempDir();
|
||||
logMessage("extracted driver from jar to " + driverPath());
|
||||
if (installBrowsers)
|
||||
@@ -68,9 +86,8 @@ public class DriverJar extends Driver {
|
||||
if (!Files.exists(driver)) {
|
||||
throw new RuntimeException("Failed to find driver: " + driver);
|
||||
}
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
|
||||
pb.environment().putAll(env);
|
||||
setRequiredEnvironmentVariables(pb);
|
||||
ProcessBuilder pb = createProcessBuilder();
|
||||
pb.command().add("install");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -89,9 +106,10 @@ public class DriverJar extends Driver {
|
||||
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
|
||||
}
|
||||
|
||||
private void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
||||
URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
|
||||
URI originalUri = classloader.getResource(
|
||||
"driver/" + platformDir()).toURI();
|
||||
URI uri = maybeExtractNestedJar(originalUri);
|
||||
|
||||
// Create zip filesystem if loading from jar.
|
||||
@@ -103,6 +121,12 @@ public class DriverJar extends Driver {
|
||||
// See https://github.com/microsoft/playwright-java/issues/306
|
||||
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
|
||||
Files.walk(srcRoot).forEach(fromPath -> {
|
||||
if (preinstalledNodePath != null) {
|
||||
String fileName = fromPath.getFileName().toString();
|
||||
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
|
||||
Path toPath = driverTempDir.resolve(relative.toString());
|
||||
try {
|
||||
|
||||
+57
-21
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
package com.microsoft.playwright.impl.driver.jar;
|
||||
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
import com.microsoft.playwright.impl.driver.jar.DriverJar;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -25,6 +25,8 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
@@ -32,6 +34,8 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.microsoft.playwright.impl.driver.jar.DriverJar.PLAYWRIGHT_NODEJS_PATH;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestInstall {
|
||||
@@ -57,6 +61,7 @@ public class TestInstall {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
System.clearProperty("playwright.driver.tmpdir");
|
||||
System.clearProperty("playwright.nodejs.path");
|
||||
// Clear system property to ensure that the default driver is loaded.
|
||||
System.clearProperty("playwright.driver.impl");
|
||||
}
|
||||
@@ -72,27 +77,20 @@ public class TestInstall {
|
||||
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
|
||||
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
|
||||
|
||||
// Reset instance field value to null for the test.
|
||||
Field field = Driver.class.getDeclaredField("instance");
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(Driver.class);
|
||||
field.set(Driver.class, null);
|
||||
|
||||
for (int i = 0; i < 2; i++){
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
|
||||
String message = exception.getMessage();
|
||||
assertTrue(message.contains("Failed to create driver"), message);
|
||||
}
|
||||
|
||||
field.set(Driver.class, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightCliInstalled() throws Exception {
|
||||
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
assertTrue(Files.exists(cli));
|
||||
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
|
||||
assertTrue(Files.exists(driver.driverPath()));
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("install");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -109,24 +107,62 @@ public class TestInstall {
|
||||
|
||||
@Test
|
||||
void playwrightDriverDefaultImpl() {
|
||||
assertDoesNotThrow(() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
|
||||
assertDoesNotThrow(() -> Driver.createAndInstall(Collections.emptyMap(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
|
||||
// Reset instance field value to null for the test.
|
||||
Field field = Driver.class.getDeclaredField("instance");
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(Driver.class);
|
||||
field.set(Driver.class, null);
|
||||
|
||||
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
|
||||
RuntimeException thrown =
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
|
||||
assertEquals("Failed to create driver", thrown.getMessage());
|
||||
}
|
||||
|
||||
field.set(Driver.class, value);
|
||||
@Test
|
||||
void canPassPreinstalledNodeJsAsSystemProperty(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
String nodePath = extractNodeJsToTemp();
|
||||
System.setProperty("playwright.nodejs.path", nodePath);
|
||||
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
|
||||
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
String nodePath = extractNodeJsToTemp();
|
||||
Driver driver = Driver.createAndInstall(singletonMap(PLAYWRIGHT_NODEJS_PATH, nodePath), false);
|
||||
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
|
||||
}
|
||||
|
||||
|
||||
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
|
||||
DriverJar auxDriver = new DriverJar();
|
||||
auxDriver.extractDriverToTempDir();
|
||||
String nodePath = auxDriver.driverPath().getParent().resolve(isWindows() ? "node.exe" : "node").toString();
|
||||
return nodePath;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
return name.contains("win");
|
||||
}
|
||||
|
||||
private static void canSpecifyPreinstalledNodeJsShared(Driver driver, Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
Path builtinNode = driver.driverPath().getParent().resolve("node");
|
||||
assertFalse(Files.exists(builtinNode), builtinNode.toString());
|
||||
Path builtinNodeExe = driver.driverPath().getParent().resolve("node.exe");
|
||||
assertFalse(Files.exists(builtinNodeExe), builtinNodeExe.toString());
|
||||
|
||||
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();
|
||||
boolean result = p.waitFor(1, TimeUnit.MINUTES);
|
||||
assertTrue(result, "Timed out waiting for version to be printed");
|
||||
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
|
||||
assertTrue(stdout.contains("Version "), stdout);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package com.microsoft.playwright.impl.driver;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
|
||||
@@ -28,6 +30,8 @@ import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestam
|
||||
* loaded from the driver-bundle module if that module is in the classpath.
|
||||
*/
|
||||
public abstract class Driver {
|
||||
protected final Map<String, String> env = new LinkedHashMap<>();
|
||||
|
||||
private static Driver instance;
|
||||
|
||||
private static class PreinstalledDriver extends Driver {
|
||||
@@ -38,7 +42,7 @@ public abstract class Driver {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
|
||||
protected void initialize(Boolean installBrowsers) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -48,22 +52,18 @@ public abstract class Driver {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = createDriver();
|
||||
logMessage("initializing driver");
|
||||
instance.initialize(env, installBrowsers);
|
||||
logMessage("driver initialized.");
|
||||
} catch (Exception exception) {
|
||||
instance = null;
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
instance = createAndInstall(env, installBrowsers);
|
||||
}
|
||||
return instance.driverPath();
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
|
||||
private void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
this.env.putAll(env);
|
||||
initialize(installBrowsers);
|
||||
}
|
||||
protected abstract void initialize(Boolean installBrowsers) throws Exception;
|
||||
|
||||
public Path driverPath() {
|
||||
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
@@ -71,13 +71,16 @@ public abstract class Driver {
|
||||
return driverDir().resolve(cliFileName);
|
||||
}
|
||||
|
||||
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
|
||||
public ProcessBuilder createProcessBuilder() {
|
||||
ProcessBuilder pb = new ProcessBuilder(driverPath().toString());
|
||||
pb.environment().putAll(env);
|
||||
pb.environment().put("PW_LANG_NAME", "java");
|
||||
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
|
||||
String version = Driver.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
}
|
||||
return pb;
|
||||
}
|
||||
|
||||
private static String getMajorJavaVersion() {
|
||||
@@ -91,8 +94,19 @@ public abstract class Driver {
|
||||
}
|
||||
return version;
|
||||
}
|
||||
public static Driver createAndInstall(Map<String, String> env, Boolean installBrowsers) {
|
||||
try {
|
||||
Driver instance = newInstance();
|
||||
logMessage("initializing driver");
|
||||
instance.initialize(env, installBrowsers);
|
||||
logMessage("driver initialized.");
|
||||
return instance;
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static Driver createDriver() throws Exception {
|
||||
private static Driver newInstance() throws Exception {
|
||||
String pathFromProperty = System.getProperty("playwright.cli.dir");
|
||||
if (pathFromProperty != null) {
|
||||
return new PreinstalledDriver(Paths.get(pathFromProperty));
|
||||
|
||||
@@ -29,10 +29,9 @@ import static java.util.Arrays.asList;
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().addAll(asList(args));
|
||||
Driver.setRequiredEnvironmentVariables(pb);
|
||||
String version = Playwright.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.microsoft.playwright.Selectors;
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -33,16 +32,21 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private Process driverProcess;
|
||||
|
||||
public static PlaywrightImpl create(CreateOptions options) {
|
||||
return createImpl(options, false);
|
||||
}
|
||||
|
||||
public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewDriverInstanceForTests) {
|
||||
Map<String, String> env = Collections.emptyMap();
|
||||
if (options != null && options.env != null) {
|
||||
env = options.env;
|
||||
}
|
||||
Driver driver = forceNewDriverInstanceForTests ?
|
||||
Driver.createAndInstall(env, true) :
|
||||
Driver.ensureDriverInstalled(env, true);
|
||||
try {
|
||||
Map<String, String> env = Collections.emptyMap();
|
||||
if (options != null && options.env != null) {
|
||||
env = options.env;
|
||||
}
|
||||
Path driver = Driver.ensureDriverInstalled(env, true);
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("run-driver");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.environment().putAll(env);
|
||||
Driver.setRequiredEnvironmentVariables(pb);
|
||||
Process p = pb.start();
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
|
||||
@@ -60,8 +60,8 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
|
||||
private static BrowserServer launchBrowserServer(BrowserType browserType) {
|
||||
try {
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
Path dir = driver.getParent();
|
||||
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
Path dir = driver.driverPath().getParent();
|
||||
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
|
||||
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
|
||||
// We launch node process directly instead of using playwright.sh script as killing the script
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.PlaywrightImpl;
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -31,12 +34,13 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestPlaywrightCreate {
|
||||
@Test
|
||||
void shouldSupportEnvSkipBrowserDownload(@TempDir Path browsersDir) throws IOException {
|
||||
void shouldSupportEnvSkipBrowserDownload(@TempDir Path browsersDir) throws IOException, NoSuchFieldException, IllegalAccessException {
|
||||
System.err.println("shouldSupportEnvSkipBrowserDownload PLAYWRIGHT_BROWSERS_PATH = " + browsersDir);
|
||||
Map<String, String> env = mapOf("PLAYWRIGHT_BROWSERS_PATH", browsersDir.toString(),
|
||||
"PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1");
|
||||
Playwright.CreateOptions options = new Playwright.CreateOptions().setEnv(env);
|
||||
|
||||
try (Playwright playwright = Playwright.create(options)) {
|
||||
try (Playwright playwright = PlaywrightImpl.createImpl(options, true)) {
|
||||
try {
|
||||
getBrowserTypeFromEnv(playwright).launch();
|
||||
fail("Did not throw");
|
||||
|
||||
Reference in New Issue
Block a user