22
33namespace Laravel \Dusk \Console ;
44
5+ use Exception ;
6+ use GuzzleHttp \Psr7 \Utils ;
57use Illuminate \Console \Command ;
8+ use Illuminate \Support \Facades \Http ;
9+ use Illuminate \Support \Str ;
610use Laravel \Dusk \OperatingSystem ;
711use Symfony \Component \Process \Process ;
812use ZipArchive ;
@@ -31,41 +35,7 @@ class ChromeDriverCommand extends Command
3135 protected $ description = 'Install the ChromeDriver binary ' ;
3236
3337 /**
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.
6939 *
7040 * @var array
7141 */
@@ -106,29 +76,6 @@ class ChromeDriverCommand extends Command
10676 */
10777 protected $ directory = __DIR__ .'/../../bin/ ' ;
10878
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-
13279 /**
13380 * Execute the console command.
13481 *
@@ -142,11 +89,11 @@ public function handle()
14289
14390 $ currentOS = OperatingSystem::id ();
14491
145- foreach ($ this -> slugs as $ os => $ slug ) {
92+ foreach (OperatingSystem:: all () as $ os ) {
14693 if ($ all || ($ os === $ currentOS )) {
147- $ archive = $ this ->download ($ version , $ slug );
94+ $ archive = $ this ->download ($ version , $ os );
14895
149- $ binary = $ this ->extract ($ archive );
96+ $ binary = $ this ->extract ($ version , $ archive );
15097
15198 $ this ->rename ($ binary , $ os );
15299 }
@@ -182,11 +129,14 @@ protected function version()
182129
183130 if ($ version < 70 ) {
184131 return $ this ->legacyVersions [$ version ];
132+ } elseif ($ version < 115 ) {
133+ return $ this ->fetchChromeVersionFromUrl ($ version );
185134 }
186135
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. ' );
190140 }
191141
192142 /**
@@ -196,22 +146,10 @@ protected function version()
196146 */
197147 protected function latestVersion ()
198148 {
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 );
213150
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. ' );
215153 }
216154
217155 /**
@@ -222,7 +160,7 @@ protected function latestVersion()
222160 */
223161 protected function detectChromeVersion ($ os )
224162 {
225- foreach ($ this -> chromeVersionCommands [ $ os] as $ command ) {
163+ foreach (OperatingSystem:: chromeVersionCommands ( $ os) as $ command ) {
226164 $ process = Process::fromShellCommandline ($ command );
227165
228166 $ process ->run ();
@@ -245,36 +183,41 @@ protected function detectChromeVersion($os)
245183 * Download the ChromeDriver archive.
246184 *
247185 * @param string $version
248- * @param string $slug
186+ * @param string $os
249187 * @return string
250188 */
251- protected function download ($ version , $ slug )
189+ protected function download ($ version , $ os )
252190 {
253- $ url = sprintf ( $ this ->downloadUrl , $ version , $ slug );
191+ $ url = $ this ->resolveChromeDriverDownloadUrl ( $ version , $ os );
254192
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 );
259201
260202 return $ archive ;
261203 }
262204
263205 /**
264206 * Extract the ChromeDriver binary from the archive and delete the archive.
265207 *
208+ * @param string $version
266209 * @param string $archive
267210 * @return string
268211 */
269- protected function extract ($ archive )
212+ protected function extract ($ version , $ archive )
270213 {
271214 $ zip = new ZipArchive ;
272215
273216 $ zip ->open ($ archive );
274217
275218 $ zip ->extractTo ($ this ->directory );
276219
277- $ binary = $ zip ->getNameIndex (0 );
220+ $ binary = $ zip ->getNameIndex (version_compare ( $ version , ' 115.0 ' , ' < ' ) ? 0 : 1 );
278221
279222 $ zip ->close ();
280223
@@ -292,33 +235,77 @@ protected function extract($archive)
292235 */
293236 protected function rename ($ binary , $ os )
294237 {
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 );
296241
297242 rename ($ this ->directory .$ binary , $ this ->directory .$ newName );
298243
299244 chmod ($ this ->directory .$ newName , 0755 );
300245 }
301246
302247 /**
303- * Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options .
248+ * Get the Chrome version from URL.
304249 *
305- * @param string $url
306- * @return string|bool
250+ * @return string
307251 */
308- protected function getUrl ( string $ url )
252+ protected function fetchChromeVersionFromUrl ( int $ version )
309253 {
310- $ contextOptions = [];
254+ return trim ((string ) $ this ->getUrl (
255+ sprintf ('https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d ' , $ version )
256+ ));
257+ }
311258
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+ }
315270
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 );
318284 }
319285
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+ }
321297
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 ();
323310 }
324311}
0 commit comments