Skip to content

Commit

Permalink
[7.x] Fix ChromeDriverCommand for ChromeDriver 115+ (laravel#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 <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* Update OperatingSystemTest.php

* Update ChromeDriverCommand.php

* formatting

* formatting

---------

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>
Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
2 people authored and Kayrah87 committed Jul 28, 2023
1 parent b82cdd4 commit 4f2866a
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 118 deletions.
25 changes: 14 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@
}
],
"require": {
"php": "^7.2|^8.0",
"php": "^8.0",
"ext-json": "*",
"ext-zip": "*",
"guzzlehttp/guzzle": "^7.2",
"php-webdriver/webdriver": "^1.9.0",
"nesbot/carbon": "^2.0",
"illuminate/console": "^6.0|^7.0|^8.0|^9.0",
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"symfony/console": "^4.3|^5.0|^6.0",
"symfony/finder": "^4.3|^5.0|^6.0",
"symfony/process": "^4.3|^5.0|^6.0",
"vlucas/phpdotenv": "^3.0|^4.0|^5.2"
"illuminate/console": "^9.0|^10.0",
"illuminate/support": "^9.0|^10.0",
"symfony/console": "^6.0",
"symfony/finder": "^6.0",
"symfony/process": "^6.0",
"vlucas/phpdotenv": "^5.2"
},
"require-dev": {
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.5.15|^8.4|^9.0",
"orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0"
"mockery/mockery": "^1.4.2",
"orchestra/testbench": "^7.0|^8.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.5.10|^10.0.1",
"psy/psysh": "^0.11.12"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
Expand All @@ -42,7 +45,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
"dev-master": "7.x-dev"
},
"laravel": {
"providers": [
Expand Down
194 changes: 89 additions & 105 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,32 +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' => [
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --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 @@ -145,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 @@ -185,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 @@ -199,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 @@ -225,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 @@ -248,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 @@ -295,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 4f2866a

Please sign in to comment.