Skip to content

Commit a926506

Browse files
[7.x] Fix ChromeDriverCommand for ChromeDriver 115+ (#1043)
* [7.x] Fix ChromeDriverCommand for ChromeDriver 115+ Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Update OperatingSystemTest.php * Update ChromeDriverCommand.php * formatting * formatting --------- Signed-off-by: Mior Muhammad Zaki <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent 35f4242 commit a926506

File tree

4 files changed

+274
-106
lines changed

4 files changed

+274
-106
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
"php": "^8.0",
1414
"ext-json": "*",
1515
"ext-zip": "*",
16-
"php-webdriver/webdriver": "^1.9.0",
17-
"nesbot/carbon": "^2.0",
16+
"guzzlehttp/guzzle": "^7.2",
1817
"illuminate/console": "^9.0|^10.0",
1918
"illuminate/support": "^9.0|^10.0",
19+
"nesbot/carbon": "^2.0",
20+
"php-webdriver/webdriver": "^1.9.0",
2021
"symfony/console": "^6.0",
2122
"symfony/finder": "^6.0",
2223
"symfony/process": "^6.0",

src/Console/ChromeDriverCommand.php

Lines changed: 89 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
namespace Laravel\Dusk\Console;
44

5+
use Exception;
6+
use GuzzleHttp\Psr7\Utils;
57
use Illuminate\Console\Command;
8+
use Illuminate\Support\Facades\Http;
9+
use Illuminate\Support\Str;
610
use Laravel\Dusk\OperatingSystem;
711
use Symfony\Component\Process\Process;
812
use 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

Comments
 (0)