1+ package jsenv ;
2+
3+ /*
4+ * Copyright (c) Microsoft Corporation.
5+ *
6+ * Licensed under the Apache License, Version 2.0 (the "License");
7+ * you may not use this file except in compliance with the License.
8+ * You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+
19+ //package com.microsoft.playwright.impl.driver.jar;
20+ // String driverImpl =
21+ // System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
22+
23+ import com .microsoft .playwright .impl .driver .Driver ;
24+
25+ import java .io .IOException ;
26+ import java .net .URI ;
27+ import java .net .URISyntaxException ;
28+ import java .nio .file .*;
29+ import java .util .Collections ;
30+ import java .util .Map ;
31+ import java .util .concurrent .TimeUnit ;
32+
33+ public class DriverJar extends Driver {
34+ private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" ;
35+ private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL" ;
36+ private final Path driverTempDir ;
37+ private Path preinstalledNodePath ;
38+
39+ public DriverJar () throws IOException {
40+ // Allow specifying custom path for the driver installation
41+ // See https://github.com/microsoft/playwright-java/issues/728
42+ String alternativeTmpdir = System .getProperty ("playwright.driver.tmpdir" );
43+ String prefix = "playwright-java-" ;
44+ driverTempDir = alternativeTmpdir == null
45+ ? Files .createTempDirectory (prefix )
46+ : Files .createTempDirectory (Paths .get (alternativeTmpdir ), prefix );
47+ driverTempDir .toFile ().deleteOnExit ();
48+ String nodePath = System .getProperty ("playwright.nodejs.path" );
49+ if (nodePath != null ) {
50+ preinstalledNodePath = Paths .get (nodePath );
51+ if (!Files .exists (preinstalledNodePath )) {
52+ throw new RuntimeException ("Invalid Node.js path specified: " + nodePath );
53+ }
54+ }
55+ logMessage ("created DriverJar: " + driverTempDir );
56+ }
57+
58+ @ Override
59+ protected void initialize (Boolean installBrowsers ) throws Exception {
60+ if (preinstalledNodePath == null && env .containsKey (PLAYWRIGHT_NODEJS_PATH )) {
61+ preinstalledNodePath = Paths .get (env .get (PLAYWRIGHT_NODEJS_PATH ));
62+ if (!Files .exists (preinstalledNodePath )) {
63+ throw new RuntimeException ("Invalid Node.js path specified: " + preinstalledNodePath );
64+ }
65+ } else if (preinstalledNodePath != null ) {
66+ // Pass the env variable to the driver process.
67+ env .put (PLAYWRIGHT_NODEJS_PATH , preinstalledNodePath .toString ());
68+ }
69+ extractDriverToTempDir ();
70+ logMessage ("extracted driver from jar to " + driverDir ());
71+ if (installBrowsers )
72+ installBrowsers (env );
73+ }
74+
75+ private void installBrowsers (Map <String , String > env ) throws IOException , InterruptedException {
76+ String skip = env .get (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD );
77+ if (skip == null ) {
78+ skip = System .getenv (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD );
79+ }
80+ if (skip != null && !"0" .equals (skip ) && !"false" .equals (skip )) {
81+ logMessage ("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set" );
82+ return ;
83+ }
84+ if (env .get (SELENIUM_REMOTE_URL ) != null || System .getenv (SELENIUM_REMOTE_URL ) != null ) {
85+ logMessage ("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set" );
86+ return ;
87+ }
88+ Path driver = driverDir ();
89+ if (!Files .exists (driver )) {
90+ throw new RuntimeException ("Failed to find driver: " + driver );
91+ }
92+ ProcessBuilder pb = createProcessBuilder ();
93+ pb .command ().add ("install" );
94+ pb .redirectError (ProcessBuilder .Redirect .INHERIT );
95+ pb .redirectOutput (ProcessBuilder .Redirect .INHERIT );
96+ Process p = pb .start ();
97+ boolean result = p .waitFor (10 , TimeUnit .MINUTES );
98+ if (!result ) {
99+ p .destroy ();
100+ throw new RuntimeException ("Timed out waiting for browsers to install" );
101+ }
102+ if (p .exitValue () != 0 ) {
103+ throw new RuntimeException ("Failed to install browsers, exit code: " + p .exitValue ());
104+ }
105+ }
106+
107+ private static boolean isExecutable (Path filePath ) {
108+ String name = filePath .getFileName ().toString ();
109+ return name .endsWith (".sh" ) || name .endsWith (".exe" ) || !name .contains ("." );
110+ }
111+
112+ private FileSystem initFileSystem (URI uri ) throws IOException {
113+ try {
114+ return FileSystems .newFileSystem (uri , Collections .emptyMap ());
115+ } catch (FileSystemAlreadyExistsException e ) {
116+ return null ;
117+ }
118+ }
119+
120+ public static URI getDriverResourceURI () throws URISyntaxException {
121+ // ClassLoader classloader = Thread.currentThread().getContextClassLoader();
122+ ClassLoader classloader = DriverJar .class .getClassLoader ();
123+ return classloader .getResource ("driver/" + platformDir ()).toURI ();
124+ }
125+
126+ void extractDriverToTempDir () throws URISyntaxException , IOException {
127+ URI originalUri = getDriverResourceURI ();
128+ URI uri = maybeExtractNestedJar (originalUri );
129+
130+ // Create zip filesystem if loading from jar.
131+ try (FileSystem fileSystem = "jar" .equals (uri .getScheme ()) ? initFileSystem (uri ) : null ) {
132+ Path srcRoot = Paths .get (uri );
133+ // jar file system's .relativize gives wrong results when used with
134+ // spring-boot-maven-plugin, convert to the default filesystem to
135+ // have predictable results.
136+ // See https://github.com/microsoft/playwright-java/issues/306
137+ Path srcRootDefaultFs = Paths .get (srcRoot .toString ());
138+ Files .walk (srcRoot ).forEach (fromPath -> {
139+ if (preinstalledNodePath != null ) {
140+ String fileName = fromPath .getFileName ().toString ();
141+ if ("node.exe" .equals (fileName ) || "node" .equals (fileName )) {
142+ return ;
143+ }
144+ }
145+ Path relative = srcRootDefaultFs .relativize (Paths .get (fromPath .toString ()));
146+ Path toPath = driverTempDir .resolve (relative .toString ());
147+ try {
148+ if (Files .isDirectory (fromPath )) {
149+ Files .createDirectories (toPath );
150+ } else {
151+ Files .copy (fromPath , toPath );
152+ if (isExecutable (toPath )) {
153+ toPath .toFile ().setExecutable (true , true );
154+ }
155+ }
156+ toPath .toFile ().deleteOnExit ();
157+ } catch (IOException e ) {
158+ throw new RuntimeException ("Failed to extract driver from " + uri + ", full uri: " + originalUri , e );
159+ }
160+ });
161+ }
162+ }
163+
164+ private URI maybeExtractNestedJar (final URI uri ) throws URISyntaxException {
165+ if (!"jar" .equals (uri .getScheme ())) {
166+ return uri ;
167+ }
168+ final String JAR_URL_SEPARATOR = "!/" ;
169+ String [] parts = uri .toString ().split ("!/" );
170+ if (parts .length != 3 ) {
171+ return uri ;
172+ }
173+ String innerJar = String .join (JAR_URL_SEPARATOR , parts [0 ], parts [1 ]);
174+ URI jarUri = new URI (innerJar );
175+ try (FileSystem fs = FileSystems .newFileSystem (jarUri , Collections .emptyMap ())) {
176+ Path fromPath = Paths .get (jarUri );
177+ Path toPath = driverTempDir .resolve (fromPath .getFileName ().toString ());
178+ Files .copy (fromPath , toPath );
179+ toPath .toFile ().deleteOnExit ();
180+ return new URI ("jar:" + toPath .toUri () + JAR_URL_SEPARATOR + parts [2 ]);
181+ } catch (IOException e ) {
182+ throw new RuntimeException ("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri , e );
183+ }
184+ }
185+
186+ private static String platformDir () {
187+ String name = System .getProperty ("os.name" ).toLowerCase ();
188+ String arch = System .getProperty ("os.arch" ).toLowerCase ();
189+
190+ if (name .contains ("windows" )) {
191+ return "win32_x64" ;
192+ }
193+ if (name .contains ("linux" )) {
194+ if (arch .equals ("aarch64" )) {
195+ return "linux-arm64" ;
196+ } else {
197+ return "linux" ;
198+ }
199+ }
200+ if (name .contains ("mac os x" )) {
201+ if (arch .equals ("aarch64" )) {
202+ return "mac-arm64" ;
203+ } else {
204+ return "mac" ;
205+ }
206+ }
207+ throw new RuntimeException ("Unexpected os.name value: " + name );
208+ }
209+
210+ @ Override
211+ public Path driverDir () {
212+ return driverTempDir ;
213+ }
214+ }
0 commit comments