Skip to content

Commit 2b8f04d

Browse files
committed
test: add classLoader test
1 parent a7830eb commit 2b8f04d

File tree

3 files changed

+337
-0
lines changed

3 files changed

+337
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.4.3</version>
9+
</parent>
10+
<groupId>com.microsoft.playwright</groupId>
11+
<artifactId>test-spring-classloader</artifactId>
12+
<version>1.50.0-SNAPSHOT</version>
13+
<name>Test Playwright With Spring Boot</name>
14+
<properties>
15+
<spring.version>2.4.3</spring.version>
16+
</properties>
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.springframework.boot</groupId>
20+
<artifactId>spring-boot-starter</artifactId>
21+
</dependency>
22+
<dependency>
23+
<groupId>com.microsoft.playwright</groupId>
24+
<artifactId>playwright</artifactId>
25+
<version>${project.version}</version>
26+
</dependency>
27+
</dependencies>
28+
<build>
29+
<plugins>
30+
<plugin>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-maven-plugin</artifactId>
33+
</plugin>
34+
</plugins>
35+
</build>
36+
</project>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.microsoft.playwright.springboottest;
18+
19+
import com.microsoft.playwright.impl.driver.Driver;
20+
21+
import java.io.IOException;
22+
import java.net.URI;
23+
import java.net.URISyntaxException;
24+
import java.nio.file.*;
25+
import java.util.Collections;
26+
import java.util.Map;
27+
import java.util.concurrent.TimeUnit;
28+
29+
public class DriverJar extends Driver {
30+
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
31+
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
32+
private final Path driverTempDir;
33+
private Path preinstalledNodePath;
34+
35+
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();
44+
String nodePath = System.getProperty("playwright.nodejs.path");
45+
if (nodePath != null) {
46+
preinstalledNodePath = Paths.get(nodePath);
47+
if (!Files.exists(preinstalledNodePath)) {
48+
throw new RuntimeException("Invalid Node.js path specified: " + nodePath);
49+
}
50+
}
51+
logMessage("created DriverJar: " + driverTempDir);
52+
}
53+
54+
@Override
55+
protected void initialize(Boolean installBrowsers) throws Exception {
56+
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
57+
preinstalledNodePath = Paths.get(env.get(PLAYWRIGHT_NODEJS_PATH));
58+
if (!Files.exists(preinstalledNodePath)) {
59+
throw new RuntimeException("Invalid Node.js path specified: " + preinstalledNodePath);
60+
}
61+
} else if (preinstalledNodePath != null) {
62+
// Pass the env variable to the driver process.
63+
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
64+
}
65+
extractDriverToTempDir();
66+
logMessage("extracted driver from jar to " + driverDir());
67+
if (installBrowsers)
68+
installBrowsers(env);
69+
}
70+
71+
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
72+
String skip = env.get(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
73+
if (skip == null) {
74+
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
75+
}
76+
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
77+
logMessage("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
78+
return;
79+
}
80+
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
81+
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
82+
return;
83+
}
84+
Path driver = driverDir();
85+
if (!Files.exists(driver)) {
86+
throw new RuntimeException("Failed to find driver: " + driver);
87+
}
88+
ProcessBuilder pb = createProcessBuilder();
89+
pb.command().add("install");
90+
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
91+
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
92+
Process p = pb.start();
93+
boolean result = p.waitFor(10, TimeUnit.MINUTES);
94+
if (!result) {
95+
p.destroy();
96+
throw new RuntimeException("Timed out waiting for browsers to install");
97+
}
98+
if (p.exitValue() != 0) {
99+
throw new RuntimeException("Failed to install browsers, exit code: " + p.exitValue());
100+
}
101+
}
102+
103+
private static boolean isExecutable(Path filePath) {
104+
String name = filePath.getFileName().toString();
105+
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
106+
}
107+
108+
private FileSystem initFileSystem(URI uri) throws IOException {
109+
try {
110+
return FileSystems.newFileSystem(uri, Collections.emptyMap());
111+
} catch (FileSystemAlreadyExistsException e) {
112+
return null;
113+
}
114+
}
115+
116+
public static URI getDriverResourceURI() throws URISyntaxException {
117+
ClassLoader classloader = com.microsoft.playwright.impl.driver.jar.DriverJar.class.getClassLoader();
118+
return classloader.getResource("driver/" + platformDir()).toURI();
119+
}
120+
121+
void extractDriverToTempDir() throws URISyntaxException, IOException {
122+
URI originalUri = getDriverResourceURI();
123+
URI uri = maybeExtractNestedJar(originalUri);
124+
125+
// Create zip filesystem if loading from jar.
126+
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? initFileSystem(uri) : null) {
127+
Path srcRoot = Paths.get(uri);
128+
// jar file system's .relativize gives wrong results when used with
129+
// spring-boot-maven-plugin, convert to the default filesystem to
130+
// have predictable results.
131+
// See https://github.com/microsoft/playwright-java/issues/306
132+
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
133+
Files.walk(srcRoot).forEach(fromPath -> {
134+
if (preinstalledNodePath != null) {
135+
String fileName = fromPath.getFileName().toString();
136+
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
137+
return;
138+
}
139+
}
140+
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
141+
Path toPath = driverTempDir.resolve(relative.toString());
142+
try {
143+
if (Files.isDirectory(fromPath)) {
144+
Files.createDirectories(toPath);
145+
} else {
146+
Files.copy(fromPath, toPath);
147+
if (isExecutable(toPath)) {
148+
toPath.toFile().setExecutable(true, true);
149+
}
150+
}
151+
toPath.toFile().deleteOnExit();
152+
} catch (IOException e) {
153+
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
154+
}
155+
});
156+
}
157+
}
158+
159+
private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
160+
if (!"jar".equals(uri.getScheme())) {
161+
return uri;
162+
}
163+
final String JAR_URL_SEPARATOR = "!/";
164+
String[] parts = uri.toString().split("!/");
165+
if (parts.length != 3) {
166+
return uri;
167+
}
168+
String innerJar = String.join(JAR_URL_SEPARATOR, parts[0], parts[1]);
169+
URI jarUri = new URI(innerJar);
170+
try (FileSystem fs = FileSystems.newFileSystem(jarUri, Collections.emptyMap())) {
171+
Path fromPath = Paths.get(jarUri);
172+
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
173+
Files.copy(fromPath, toPath);
174+
toPath.toFile().deleteOnExit();
175+
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
176+
} catch (IOException e) {
177+
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
178+
}
179+
}
180+
181+
private static String platformDir() {
182+
String name = System.getProperty("os.name").toLowerCase();
183+
String arch = System.getProperty("os.arch").toLowerCase();
184+
185+
if (name.contains("windows")) {
186+
return "win32_x64";
187+
}
188+
if (name.contains("linux")) {
189+
if (arch.equals("aarch64")) {
190+
return "linux-arm64";
191+
} else {
192+
return "linux";
193+
}
194+
}
195+
if (name.contains("mac os x")) {
196+
if (arch.equals("aarch64")) {
197+
return "mac-arm64";
198+
} else {
199+
return "mac";
200+
}
201+
}
202+
throw new RuntimeException("Unexpected os.name value: " + name);
203+
}
204+
205+
@Override
206+
public Path driverDir() {
207+
return driverTempDir;
208+
}
209+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.microsoft.playwright.springboottest;
2+
3+
import com.microsoft.playwright.*;
4+
import org.springframework.boot.CommandLineRunner;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
8+
import java.util.concurrent.CompletableFuture;
9+
10+
@SpringBootApplication
11+
public class TestApp implements CommandLineRunner {
12+
13+
public static void main(String[] args) {
14+
SpringApplication.run(TestApp.class, args);
15+
}
16+
17+
public void run(String... args) {
18+
System.out.println("Starting original Playwright test...");
19+
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
20+
try (Playwright playwright = Playwright.create()) {
21+
System.out.println("original Playwright test started, waiting for completion...");
22+
BrowserType browserType = getBrowserTypeFromEnv(playwright);
23+
System.out.println("Running original test with " + browserType.name());
24+
Browser browser = browserType.launch();
25+
BrowserContext context = browser.newContext();
26+
Page page = context.newPage();
27+
System.out.println(page.evaluate("'SUCCESS: did evaluate in page'"));
28+
} catch (Exception e) {
29+
System.out.println("FAILED: " + e.toString());
30+
for (StackTraceElement ste : e.getStackTrace()) {
31+
System.out.println("\tat " + ste);
32+
}
33+
}
34+
});
35+
36+
System.out.println("original Playwright test is running asynchronously, main thread will wait for it to complete.");
37+
38+
voidCompletableFuture.join();
39+
40+
System.out.println("original Playwright test completed.");
41+
42+
43+
System.out.println("Starting new Playwright test...");
44+
45+
// Set the new driver implementation to use the DriverJar class
46+
System.setProperty( "playwright.driver.impl", "com.microsoft.playwright.springboottest.DriverJar" );
47+
48+
CompletableFuture<Void> voidCompletableFuture2 = CompletableFuture.runAsync(() -> {
49+
try (Playwright playwright = Playwright.create()) {
50+
System.out.println("new Playwright test started, waiting for completion...");
51+
BrowserType browserType = getBrowserTypeFromEnv(playwright);
52+
System.out.println("Running new test with " + browserType.name());
53+
Browser browser = browserType.launch();
54+
BrowserContext context = browser.newContext();
55+
Page page = context.newPage();
56+
System.out.println(page.evaluate("'SUCCESS: did evaluate in page'"));
57+
} catch (Exception e) {
58+
System.out.println("FAILED: " + e.toString());
59+
for (StackTraceElement ste : e.getStackTrace()) {
60+
System.out.println("\tat " + ste);
61+
}
62+
}
63+
});
64+
65+
System.out.println("new Playwright test is running asynchronously, main thread will wait for it to complete.");
66+
67+
voidCompletableFuture2.join();
68+
69+
System.out.println("new Playwright test completed.");
70+
71+
}
72+
73+
static BrowserType getBrowserTypeFromEnv(Playwright playwright) {
74+
String browserName = System.getenv("BROWSER");
75+
76+
if (browserName == null) {
77+
browserName = "chromium";
78+
}
79+
80+
switch (browserName) {
81+
case "webkit":
82+
return playwright.webkit();
83+
case "firefox":
84+
return playwright.firefox();
85+
case "chromium":
86+
return playwright.chromium();
87+
default:
88+
throw new IllegalArgumentException("Unknown browser: " + browserName);
89+
}
90+
}
91+
92+
}

0 commit comments

Comments
 (0)