Skip to content

Commit

Permalink
[7.x] Fix ChromeDriverCommand for ChromeDriver 115+ (#1043)
Browse files Browse the repository at this point in the history
* [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]>
  • Loading branch information
crynobone and taylorotwell authored Jul 24, 2023
1 parent 35f4242 commit a926506
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 106 deletions.
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"php": "^8.0",
"ext-json": "*",
"ext-zip": "*",
"php-webdriver/webdriver": "^1.9.0",
"nesbot/carbon": "^2.0",
"guzzlehttp/guzzle": "^7.2",
"illuminate/console": "^9.0|^10.0",
"illuminate/support": "^9.0|^10.0",
"nesbot/carbon": "^2.0",
"php-webdriver/webdriver": "^1.9.0",
"symfony/console": "^6.0",
"symfony/finder": "^6.0",
"symfony/process": "^6.0",
Expand Down
191 changes: 89 additions & 102 deletions src/Console/ChromeDriverCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace Laravel\Dusk\Console;

use Exception;
use GuzzleHttp\Psr7\Utils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Laravel\Dusk\OperatingSystem;
use Symfony\Component\Process\Process;
use ZipArchive;
Expand Down Expand Up @@ -31,41 +35,7 @@ class ChromeDriverCommand extends Command
protected $description = 'Install the ChromeDriver binary';

/**
* URL to the latest stable release version.
*
* @var string
*/
protected $latestVersionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE';

/**
* URL to the latest release version for a major Chrome version.
*
* @var string
*/
protected $versionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d';

/**
* URL to the ChromeDriver download.
*
* @var string
*/
protected $downloadUrl = 'https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip';

/**
* Download slugs for the available operating systems.
*
* @var array
*/
protected $slugs = [
'linux' => 'linux64',
'mac' => 'mac64',
'mac-intel' => 'mac64',
'mac-arm' => 'mac_arm64',
'win' => 'win32',
];

/**
* The legacy versions for the ChromeDriver.
* The legacy versions for ChromeDriver.
*
* @var array
*/
Expand Down Expand Up @@ -106,29 +76,6 @@ class ChromeDriverCommand extends Command
*/
protected $directory = __DIR__.'/../../bin/';

/**
* The default commands to detect the installed Chrome / Chromium version.
*
* @var array
*/
protected $chromeVersionCommands = [
'linux' => [
'/usr/bin/google-chrome --version',
'/usr/bin/chromium-browser --version',
'/usr/bin/chromium --version',
'/usr/bin/google-chrome-stable --version',
],
'mac-intel' => [
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
],
'mac-arm' => [
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
],
'win' => [
'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version',
],
];

/**
* Execute the console command.
*
Expand All @@ -142,11 +89,11 @@ public function handle()

$currentOS = OperatingSystem::id();

foreach ($this->slugs as $os => $slug) {
foreach (OperatingSystem::all() as $os) {
if ($all || ($os === $currentOS)) {
$archive = $this->download($version, $slug);
$archive = $this->download($version, $os);

$binary = $this->extract($archive);
$binary = $this->extract($version, $archive);

$this->rename($binary, $os);
}
Expand Down Expand Up @@ -182,11 +129,14 @@ protected function version()

if ($version < 70) {
return $this->legacyVersions[$version];
} elseif ($version < 115) {
return $this->fetchChromeVersionFromUrl($version);
}

return trim($this->getUrl(
sprintf($this->versionUrl, $version)
));
$milestones = $this->resolveChromeVersionsPerMilestone();

return $milestones['milestones'][$version]['version']
?? throw new Exception('Could not determine the ChromeDriver version.');
}

/**
Expand All @@ -196,22 +146,10 @@ protected function version()
*/
protected function latestVersion()
{
$streamOptions = [];

if ($this->option('ssl-no-verify')) {
$streamOptions = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
];
}

if ($this->option('proxy')) {
$streamOptions['http'] = ['proxy' => $this->option('proxy'), 'request_fulluri' => true];
}
$versions = json_decode($this->getUrl('https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json'), true);

return trim(file_get_contents($this->latestVersionUrl, false, stream_context_create($streamOptions)));
return $versions['channels']['Stable']['version']
?? throw new Exception('Could not get the latest ChromeDriver version.');
}

/**
Expand All @@ -222,7 +160,7 @@ protected function latestVersion()
*/
protected function detectChromeVersion($os)
{
foreach ($this->chromeVersionCommands[$os] as $command) {
foreach (OperatingSystem::chromeVersionCommands($os) as $command) {
$process = Process::fromShellCommandline($command);

$process->run();
Expand All @@ -245,36 +183,41 @@ protected function detectChromeVersion($os)
* Download the ChromeDriver archive.
*
* @param string $version
* @param string $slug
* @param string $os
* @return string
*/
protected function download($version, $slug)
protected function download($version, $os)
{
$url = sprintf($this->downloadUrl, $version, $slug);
$url = $this->resolveChromeDriverDownloadUrl($version, $os);

file_put_contents(
$archive = $this->directory.'chromedriver.zip',
$this->getUrl($url)
);
$resource = Utils::tryFopen($archive = $this->directory.'chromedriver.zip', 'w');

Http::withOptions(array_merge([
'verify' => $this->option('ssl-no-verify') === false,
'sink' => $resource,
]), array_filter([
'proxy' => $this->option('proxy'),
]))->get($url);

return $archive;
}

/**
* Extract the ChromeDriver binary from the archive and delete the archive.
*
* @param string $version
* @param string $archive
* @return string
*/
protected function extract($archive)
protected function extract($version, $archive)
{
$zip = new ZipArchive;

$zip->open($archive);

$zip->extractTo($this->directory);

$binary = $zip->getNameIndex(0);
$binary = $zip->getNameIndex(version_compare($version, '115.0', '<') ? 0 : 1);

$zip->close();

Expand All @@ -292,33 +235,77 @@ protected function extract($archive)
*/
protected function rename($binary, $os)
{
$newName = str_replace('chromedriver', 'chromedriver-'.$os, $binary);
$newName = Str::contains($binary, DIRECTORY_SEPARATOR)
? Str::after(str_replace('chromedriver', 'chromedriver-'.$os, $binary), DIRECTORY_SEPARATOR)
: str_replace('chromedriver', 'chromedriver-'.$os, $binary);

rename($this->directory.$binary, $this->directory.$newName);

chmod($this->directory.$newName, 0755);
}

/**
* Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options.
* Get the Chrome version from URL.
*
* @param string $url
* @return string|bool
* @return string
*/
protected function getUrl(string $url)
protected function fetchChromeVersionFromUrl(int $version)
{
$contextOptions = [];
return trim((string) $this->getUrl(
sprintf('https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d', $version)
));
}

if ($this->option('proxy')) {
$contextOptions['http'] = ['proxy' => $this->option('proxy'), 'request_fulluri' => true];
}
/**
* Get the Chrome versions per milestone.
*
* @return array
*/
protected function resolveChromeVersionsPerMilestone()
{
return json_decode(
$this->getUrl('https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone-with-downloads.json'), true
);
}

if ($this->option('ssl-no-verify')) {
$contextOptions['ssl'] = ['verify_peer' => false];
/**
* Resolve the download URL.
*
* @return string
*
* @throws \Exception
*/
protected function resolveChromeDriverDownloadUrl(string $version, string $os)
{
$slug = OperatingSystem::chromeDriverSlug($os, $version);

if (version_compare($version, '115.0', '<')) {
return sprintf('https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip', $version, $slug);
}

$streamContext = stream_context_create($contextOptions);
$milestone = (int) $version;

$versions = $this->resolveChromeVersionsPerMilestone();

/** @var array<string, mixed> $chromedrivers */
$chromedrivers = $versions['milestones'][$milestone]['downloads']['chromedriver']
?? throw new Exception('Could not get the ChromeDriver version.');

return collect($chromedrivers)->firstWhere('platform', $slug)['url']
?? throw new Exception('Could not get the ChromeDriver version.');
}

return file_get_contents($url, false, $streamContext);
/**
* Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options.
*
* @return string
*/
protected function getUrl(string $url)
{
return Http::withOptions(array_merge([
'verify' => $this->option('ssl-no-verify') === false,
]), array_filter([
'proxy' => $this->option('proxy'),
]))->get($url)->body();
}
}
Loading

0 comments on commit a926506

Please sign in to comment.