2
2
3
3
namespace Laravel \Dusk \Console ;
4
4
5
+ use Exception ;
6
+ use GuzzleHttp \Psr7 \Utils ;
5
7
use Illuminate \Console \Command ;
8
+ use Illuminate \Support \Facades \Http ;
9
+ use Illuminate \Support \Str ;
6
10
use Laravel \Dusk \OperatingSystem ;
7
11
use Symfony \Component \Process \Process ;
8
12
use ZipArchive ;
@@ -31,41 +35,7 @@ class ChromeDriverCommand extends Command
31
35
protected $ description = 'Install the ChromeDriver binary ' ;
32
36
33
37
/**
34
- * URL to the latest stable release version.
35
- *
36
- * @var string
37
- */
38
- protected $ latestVersionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE ' ;
39
-
40
- /**
41
- * URL to the latest release version for a major Chrome version.
42
- *
43
- * @var string
44
- */
45
- protected $ versionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d ' ;
46
-
47
- /**
48
- * URL to the ChromeDriver download.
49
- *
50
- * @var string
51
- */
52
- protected $ downloadUrl = 'https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip ' ;
53
-
54
- /**
55
- * Download slugs for the available operating systems.
56
- *
57
- * @var array
58
- */
59
- protected $ slugs = [
60
- 'linux ' => 'linux64 ' ,
61
- 'mac ' => 'mac64 ' ,
62
- 'mac-intel ' => 'mac64 ' ,
63
- 'mac-arm ' => 'mac_arm64 ' ,
64
- 'win ' => 'win32 ' ,
65
- ];
66
-
67
- /**
68
- * The legacy versions for the ChromeDriver.
38
+ * The legacy versions for ChromeDriver.
69
39
*
70
40
* @var array
71
41
*/
@@ -106,29 +76,6 @@ class ChromeDriverCommand extends Command
106
76
*/
107
77
protected $ directory = __DIR__ .'/../../bin/ ' ;
108
78
109
- /**
110
- * The default commands to detect the installed Chrome / Chromium version.
111
- *
112
- * @var array
113
- */
114
- protected $ chromeVersionCommands = [
115
- 'linux ' => [
116
- '/usr/bin/google-chrome --version ' ,
117
- '/usr/bin/chromium-browser --version ' ,
118
- '/usr/bin/chromium --version ' ,
119
- '/usr/bin/google-chrome-stable --version ' ,
120
- ],
121
- 'mac-intel ' => [
122
- '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version ' ,
123
- ],
124
- 'mac-arm ' => [
125
- '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version ' ,
126
- ],
127
- 'win ' => [
128
- 'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version ' ,
129
- ],
130
- ];
131
-
132
79
/**
133
80
* Execute the console command.
134
81
*
@@ -142,11 +89,11 @@ public function handle()
142
89
143
90
$ currentOS = OperatingSystem::id ();
144
91
145
- foreach ($ this -> slugs as $ os => $ slug ) {
92
+ foreach (OperatingSystem:: all () as $ os ) {
146
93
if ($ all || ($ os === $ currentOS )) {
147
- $ archive = $ this ->download ($ version , $ slug );
94
+ $ archive = $ this ->download ($ version , $ os );
148
95
149
- $ binary = $ this ->extract ($ archive );
96
+ $ binary = $ this ->extract ($ version , $ archive );
150
97
151
98
$ this ->rename ($ binary , $ os );
152
99
}
@@ -182,11 +129,14 @@ protected function version()
182
129
183
130
if ($ version < 70 ) {
184
131
return $ this ->legacyVersions [$ version ];
132
+ } elseif ($ version < 115 ) {
133
+ return $ this ->fetchChromeVersionFromUrl ($ version );
185
134
}
186
135
187
- return trim ($ this ->getUrl (
188
- sprintf ($ this ->versionUrl , $ version )
189
- ));
136
+ $ milestones = $ this ->resolveChromeVersionsPerMilestone ();
137
+
138
+ return $ milestones ['milestones ' ][$ version ]['version ' ]
139
+ ?? throw new Exception ('Could not determine the ChromeDriver version. ' );
190
140
}
191
141
192
142
/**
@@ -196,22 +146,10 @@ protected function version()
196
146
*/
197
147
protected function latestVersion ()
198
148
{
199
- $ streamOptions = [];
200
-
201
- if ($ this ->option ('ssl-no-verify ' )) {
202
- $ streamOptions = [
203
- 'ssl ' => [
204
- 'verify_peer ' => false ,
205
- 'verify_peer_name ' => false ,
206
- ],
207
- ];
208
- }
209
-
210
- if ($ this ->option ('proxy ' )) {
211
- $ streamOptions ['http ' ] = ['proxy ' => $ this ->option ('proxy ' ), 'request_fulluri ' => true ];
212
- }
149
+ $ versions = json_decode ($ this ->getUrl ('https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json ' ), true );
213
150
214
- return trim (file_get_contents ($ this ->latestVersionUrl , false , stream_context_create ($ streamOptions )));
151
+ return $ versions ['channels ' ]['Stable ' ]['version ' ]
152
+ ?? throw new Exception ('Could not get the latest ChromeDriver version. ' );
215
153
}
216
154
217
155
/**
@@ -222,7 +160,7 @@ protected function latestVersion()
222
160
*/
223
161
protected function detectChromeVersion ($ os )
224
162
{
225
- foreach ($ this -> chromeVersionCommands [ $ os] as $ command ) {
163
+ foreach (OperatingSystem:: chromeVersionCommands ( $ os) as $ command ) {
226
164
$ process = Process::fromShellCommandline ($ command );
227
165
228
166
$ process ->run ();
@@ -245,36 +183,41 @@ protected function detectChromeVersion($os)
245
183
* Download the ChromeDriver archive.
246
184
*
247
185
* @param string $version
248
- * @param string $slug
186
+ * @param string $os
249
187
* @return string
250
188
*/
251
- protected function download ($ version , $ slug )
189
+ protected function download ($ version , $ os )
252
190
{
253
- $ url = sprintf ( $ this ->downloadUrl , $ version , $ slug );
191
+ $ url = $ this ->resolveChromeDriverDownloadUrl ( $ version , $ os );
254
192
255
- file_put_contents (
256
- $ archive = $ this ->directory .'chromedriver.zip ' ,
257
- $ this ->getUrl ($ url )
258
- );
193
+ $ resource = Utils::tryFopen ($ archive = $ this ->directory .'chromedriver.zip ' , 'w ' );
194
+
195
+ Http::withOptions (array_merge ([
196
+ 'verify ' => $ this ->option ('ssl-no-verify ' ) === false ,
197
+ 'sink ' => $ resource ,
198
+ ]), array_filter ([
199
+ 'proxy ' => $ this ->option ('proxy ' ),
200
+ ]))->get ($ url );
259
201
260
202
return $ archive ;
261
203
}
262
204
263
205
/**
264
206
* Extract the ChromeDriver binary from the archive and delete the archive.
265
207
*
208
+ * @param string $version
266
209
* @param string $archive
267
210
* @return string
268
211
*/
269
- protected function extract ($ archive )
212
+ protected function extract ($ version , $ archive )
270
213
{
271
214
$ zip = new ZipArchive ;
272
215
273
216
$ zip ->open ($ archive );
274
217
275
218
$ zip ->extractTo ($ this ->directory );
276
219
277
- $ binary = $ zip ->getNameIndex (0 );
220
+ $ binary = $ zip ->getNameIndex (version_compare ( $ version , ' 115.0 ' , ' < ' ) ? 0 : 1 );
278
221
279
222
$ zip ->close ();
280
223
@@ -292,33 +235,77 @@ protected function extract($archive)
292
235
*/
293
236
protected function rename ($ binary , $ os )
294
237
{
295
- $ newName = str_replace ('chromedriver ' , 'chromedriver- ' .$ os , $ binary );
238
+ $ newName = Str::contains ($ binary , DIRECTORY_SEPARATOR )
239
+ ? Str::after (str_replace ('chromedriver ' , 'chromedriver- ' .$ os , $ binary ), DIRECTORY_SEPARATOR )
240
+ : str_replace ('chromedriver ' , 'chromedriver- ' .$ os , $ binary );
296
241
297
242
rename ($ this ->directory .$ binary , $ this ->directory .$ newName );
298
243
299
244
chmod ($ this ->directory .$ newName , 0755 );
300
245
}
301
246
302
247
/**
303
- * Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options .
248
+ * Get the Chrome version from URL.
304
249
*
305
- * @param string $url
306
- * @return string|bool
250
+ * @return string
307
251
*/
308
- protected function getUrl ( string $ url )
252
+ protected function fetchChromeVersionFromUrl ( int $ version )
309
253
{
310
- $ contextOptions = [];
254
+ return trim ((string ) $ this ->getUrl (
255
+ sprintf ('https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d ' , $ version )
256
+ ));
257
+ }
311
258
312
- if ($ this ->option ('proxy ' )) {
313
- $ contextOptions ['http ' ] = ['proxy ' => $ this ->option ('proxy ' ), 'request_fulluri ' => true ];
314
- }
259
+ /**
260
+ * Get the Chrome versions per milestone.
261
+ *
262
+ * @return array
263
+ */
264
+ protected function resolveChromeVersionsPerMilestone ()
265
+ {
266
+ return json_decode (
267
+ $ this ->getUrl ('https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone-with-downloads.json ' ), true
268
+ );
269
+ }
315
270
316
- if ($ this ->option ('ssl-no-verify ' )) {
317
- $ contextOptions ['ssl ' ] = ['verify_peer ' => false ];
271
+ /**
272
+ * Resolve the download URL.
273
+ *
274
+ * @return string
275
+ *
276
+ * @throws \Exception
277
+ */
278
+ protected function resolveChromeDriverDownloadUrl (string $ version , string $ os )
279
+ {
280
+ $ slug = OperatingSystem::chromeDriverSlug ($ os , $ version );
281
+
282
+ if (version_compare ($ version , '115.0 ' , '< ' )) {
283
+ return sprintf ('https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip ' , $ version , $ slug );
318
284
}
319
285
320
- $ streamContext = stream_context_create ($ contextOptions );
286
+ $ milestone = (int ) $ version ;
287
+
288
+ $ versions = $ this ->resolveChromeVersionsPerMilestone ();
289
+
290
+ /** @var array<string, mixed> $chromedrivers */
291
+ $ chromedrivers = $ versions ['milestones ' ][$ milestone ]['downloads ' ]['chromedriver ' ]
292
+ ?? throw new Exception ('Could not get the ChromeDriver version. ' );
293
+
294
+ return collect ($ chromedrivers )->firstWhere ('platform ' , $ slug )['url ' ]
295
+ ?? throw new Exception ('Could not get the ChromeDriver version. ' );
296
+ }
321
297
322
- return file_get_contents ($ url , false , $ streamContext );
298
+ /**
299
+ * Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options.
300
+ *
301
+ * @return string
302
+ */
303
+ protected function getUrl (string $ url )
304
+ {
305
+ return Http::withOptions (array_merge ([
306
+ 'verify ' => $ this ->option ('ssl-no-verify ' ) === false ,
307
+ ]), array_filter ([
308
+ 'proxy ' => $ this ->option ('proxy ' ),
309
+ ]))->get ($ url )->body ();
323
310
}
324
311
}
0 commit comments