feat(driver): launch driver per playwright instance (#75)

This commit is contained in:
Yury Semikhatsky 2020-11-17 17:42:10 -08:00 committed by GitHub
parent 6676cdd5b2
commit c05b6409f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 38 deletions

View File

@ -1,12 +1,12 @@
/* /*
* Copyright (c) Microsoft Corporation. * Copyright (c) Microsoft Corporation.
* <p> *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* <p> *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* <p> *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -18,10 +18,9 @@ package com.microsoft.playwright;
import com.microsoft.playwright.impl.PlaywrightImpl; import com.microsoft.playwright.impl.PlaywrightImpl;
import java.io.IOException;
import java.util.Map; import java.util.Map;
public interface Playwright { public interface Playwright extends AutoCloseable {
static Playwright create() { static Playwright create() {
return PlaywrightImpl.create(); return PlaywrightImpl.create();
} }
@ -33,4 +32,7 @@ public interface Playwright {
Map<String, DeviceDescriptor> devices(); Map<String, DeviceDescriptor> devices();
Selectors selectors(); Selectors selectors();
@Override
void close() throws Exception;
} }

View File

@ -21,7 +21,7 @@ import java.io.File;
import java.nio.file.Paths; import java.nio.file.Paths;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create(); Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch(); Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext( BrowserContext context = browser.newContext(
@ -31,5 +31,6 @@ public class Main {
page.click("text=check feature status"); page.click("text=check feature status");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("s.png"))); page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("s.png")));
browser.close(); browser.close();
playwright.close();
} }
} }

View File

@ -29,28 +29,32 @@ import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PlaywrightImpl extends ChannelOwner implements Playwright { public class PlaywrightImpl extends ChannelOwner implements Playwright {
private static Path driverTempDir; private static Path driverTempDir;
private Process driverProcess;
public static PlaywrightImpl create() { public static PlaywrightImpl create() {
try { try {
Path driver = ensureDriverExtracted(); Path driver = ensureDriverInstalled();
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver"); ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
pb.redirectError(ProcessBuilder.Redirect.INHERIT); pb.redirectError(ProcessBuilder.Redirect.INHERIT);
// pb.environment().put("DEBUG", "pw:pro*"); // pb.environment().put("DEBUG", "pw:pro*");
Process p = pb.start(); Process p = pb.start();
Connection connection = new Connection(p.getInputStream(), p.getOutputStream()); Connection connection = new Connection(p.getInputStream(), p.getOutputStream());
return (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright"); PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
} catch (IOException e) { result.driverProcess = p;
return result;
} catch (IOException | InterruptedException | URISyntaxException e) {
throw new PlaywrightException("Failed to launch driver", e); throw new PlaywrightException("Failed to launch driver", e);
} }
} }
private static Path ensureDriverExtracted() { private static synchronized Path ensureDriverInstalled() throws IOException, InterruptedException, URISyntaxException {
if (driverTempDir == null) { if (driverTempDir == null) {
try {
driverTempDir = Files.createTempDirectory("playwright-java-"); driverTempDir = Files.createTempDirectory("playwright-java-");
driverTempDir.toFile().deleteOnExit(); driverTempDir.toFile().deleteOnExit();
ClassLoader classloader = Thread.currentThread().getContextClassLoader(); ClassLoader classloader = Thread.currentThread().getContextClassLoader();
@ -59,14 +63,20 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
try { try {
extractResource(filePath, driverTempDir); extractResource(filePath, driverTempDir);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + path, e); throw new PlaywrightException("Failed to extract driver from " + path, e);
} }
}); });
} catch (IOException | URISyntaxException e) {
throw new PlaywrightException("Failed to launch driver", e); Path driver = driverTempDir.resolve("playwright-cli");
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(10, TimeUnit.MINUTES);
if (!result) {
System.err.println("Timed out waiting for browsers to install");
} }
} }
// TODO: remove dir on exit
return driverTempDir.resolve("playwright-cli"); return driverTempDir.resolve("playwright-cli");
} }
@ -75,7 +85,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
Files.copy(from, path); Files.copy(from, path);
path.toFile().setExecutable(true); path.toFile().setExecutable(true);
path.toFile().deleteOnExit(); path.toFile().deleteOnExit();
System.out.println("extracting: " + from.toString() + " to " + path.toString()); // System.out.println("extracting: " + from.toString() + " to " + path.toString());
return path; return path;
} }
@ -85,7 +95,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final Selectors selectors; private final Selectors selectors;
private final Map<String, DeviceDescriptor> devices = new HashMap<>(); private final Map<String, DeviceDescriptor> devices = new HashMap<>();
public PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString()); chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString()); firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
@ -126,11 +136,13 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
return selectors; return selectors;
} }
public void close() { @Override
try { public void close() throws Exception {
connection.close(); connection.close();
} catch (IOException e) { // playwright-cli will exit when its stdin is closed, we wait for that.
throw new PlaywrightException("Failed to close", e); boolean didClose = driverProcess.waitFor(30, TimeUnit.SECONDS);
if (!didClose) {
System.err.println("WARNING: Timed out while waiting for driver process to exit");
} }
} }
} }

View File

@ -69,16 +69,19 @@ public class Transport {
return; return;
} }
isClosed = true; isClosed = true;
readerThread.interrupt(); // We interrupt only the outgoing pipe and keep reader thread running as
readerThread.in.close(); // otherwise child process may block on writing to its stdout and never
writerThread.interrupt(); // exit (observed on Windows).
readerThread.isClosing = true;
writerThread.out.close(); writerThread.out.close();
writerThread.interrupt();
} }
} }
class ReaderThread extends Thread { class ReaderThread extends Thread {
final DataInputStream in; private final DataInputStream in;
private final BlockingQueue<String> queue; private final BlockingQueue<String> queue;
volatile boolean isClosing;
private static int readIntLE(DataInputStream in) throws IOException { private static int readIntLE(DataInputStream in) throws IOException {
int ch1 = in.read(); int ch1 = in.read();
@ -103,8 +106,9 @@ class ReaderThread extends Thread {
try { try {
queue.put(readMessage()); queue.put(readMessage());
} catch (IOException e) { } catch (IOException e) {
if (!isInterrupted()) if (!isInterrupted() && !isClosing) {
e.printStackTrace(); e.printStackTrace();
}
break; break;
} catch (InterruptedException e) { } catch (InterruptedException e) {
break; break;

View File

@ -69,6 +69,12 @@ public class TestBase {
httpsServer = null; httpsServer = null;
} }
@AfterAll
static void closePlaywright() throws Exception {
playwright.close();
playwright = null;
}
@BeforeEach @BeforeEach
void createContextAndPage() { void createContextAndPage() {
server.reset(); server.reset();