Skip to content

Commit

Permalink
[MERGE]: Develop into main
Browse files Browse the repository at this point in the history
  • Loading branch information
asciito authored Oct 25, 2023
2 parents 0796441 + 9b8324b commit c2b1d01
Show file tree
Hide file tree
Showing 4 changed files with 541 additions and 4 deletions.
123 changes: 120 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This is a **personal project**, but the community can also contribute to this pr

---

## ⚠️ THE `CLI` IS NOT READY YET, DON'T USE IN PRODUCTION ⚠️

## The why?

It's simple, because I need a tool to manage my Chrome Driver and Chrome Browser, but... the reason behind that
Expand All @@ -23,8 +25,9 @@ with the correct Browser Version, is painful (I **HATE** manual tasks), I decide
## Documentation

* [Commands](#commands)
* [Install Google Chrome Browser](#install-google-chrome-browser)
* [Install Google Chrome Driver](#install-google-chrome-driver)
* [Install Google Chrome Browser](#install-google-chrome-browser): `install:browser`
* [Install Google Chrome Driver](#install-google-chrome-driver): `install:driver`
* [Manage Google Chrome Driver](#manage-google-chrome-driver): `manage:driver`

### Commands

Expand Down Expand Up @@ -82,7 +85,121 @@ any other version to download (if is available).
> **Note**: You can check the available versions on this [API endpoint](https://googlechromelabs.github.io/chrome-for-testing/known-good-versions.json) 👈, but keep in mind
> that for `chromedriver` the versions starts at `115.0.5763.0`, so any version below that we will not have access to the binary download link (for now).
---
<br>

#### Manage Google Chrome Driver

If you want to manage one instance or more of Google Chrome Browser, this could be a time-consuming task, and to simplify
this task, we have several action in one command.


The command `./google-for-testing manage:driver [--] [<action>]` have 6 actions to manage a Google Chorme Driver.

<details>
<summary><code>start</code> action</summary>

The first action is `start`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver start
```

This will start a new instance of Chrome Driver in port `9515` (by default).
</details>

<details>
<summary><code>stop</code> action</summary>

The second action is `stop`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver stop
```

This will stop the instance of Chrome Driver in port `9515` (by default).
</details>


<details>
<summary><code>restart</code> action</summary>

The third action is `restart`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver restart
```

This will restart the instance of Chrome Driver in port `9515` (by default).
</details>

<details>
<summary><code>status</code> action</summary>

The fourth action is `status`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver status
```

This will check the health of the Chrome Driver instance in port `9515` (by default).
</details>


<details>
<summary><code>list</code> action</summary>

The fifth action is `list`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver list
```

This will list all the Chrome Driver instances in a table. This table will have the
`PID` and `PORT`.

> **Note**:
> This command will list only the instances spin-up by this CLI.
</details>
<details>
<summary><code>kill</code> action</summary>

The sixth action is `kill`, and this is as simple as running the next command:

```bash
./google-for-testing manage:driver kill
```

This will search for all the instances of Chrome Driver in different ports, and then kill all the process.

> **Note**:
> This action will ask you for your permission to do it.
</details>
These are the six actions available in the command `manage:driver`, and we have two options we can use in conjunction
with these actions. For example, if you need to spin-up 2 or more instances of Chrome Driver, you
need to specify the ports where you need to spin up the servers with the option `--port` or `-p`.

```bash
./google-for-testing manage:driver start --port=9515 --port=9516
```

This will spin-up two servers, one in port `9515`, and the second one in port `9516`. As I said, this option can
be used with the other three other actions: `stop`, `restart`, and `status`.

> **Note**:
> This comand can only be use with the first four actions: `start`, `stop`, `restart`, and `status`.
The second one is just to specify where to search for the Chrome Driver binary, you just need to specify the option
`--path`.

```bash
./google-for-testing manage:driver start --path=/some/directory/path
```

This option will only search for the binary `chromedriver` in the path specify, so be sure to have this binary available
and named correctly.


## License

Expand Down
251 changes: 251 additions & 0 deletions app/Commands/DriverManagerCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<?php

namespace App\Commands;

use App\OperatingSystem;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;

use function Laravel\Prompts\error;
use function Laravel\Prompts\info;
use function Laravel\Prompts\intro;
use function Laravel\Prompts\select;
use function Laravel\Prompts\warning;

class DriverManagerCommand extends Command
{
protected $signature = 'manage:driver
{action? : The action you want to perform on Google\'s Chrome Driver}
{--p|port=* : The port from where to start a new server}
{--path= : The absolute path of where to find Google Chrome Driver binary}';

protected $description = 'Manage Google Chrome Driver';

protected string $port = '9515';

protected array $platforms = [
'linux' => 'linux64',
'mac-arm' => 'mac-arm64',
'mac-intel' => 'mac-x64',
'win' => 'win64',
];

protected array $commands = [
'start' => './chromedriver --log-level=ALL --port={port} &',
'pid' => "ps aux | grep '[c]hromedriver --log-level=ALL {options}' | awk '{print $2,$13}'",
'stop' => 'kill -9 {pid}',
];
public function handle(): int
{
$action = $this->argument('action') ?? select('Select an action to perform', [
'start' => 'Start a new server',
'stop' => 'Stop a server',
'restart' => 'Restart a server',
'status' => 'Status of a server',
'list' => 'List all the server',
'kill' => 'Kill all the servers',
]);
$callable = match ($action) {
'start' => $this->start(...),
'stop' => $this->stop(...),
'restart' => $this->restart(...),
'status' => $this->status(...),
'list' => $this->list(...),
'kill' => $this->kill(...),
};
if ($action === 'kill' || $action === 'list') {
return $callable();
}
return $this->getPorts()->map(fn (string $port) => $callable(port: $port))
// Reduce the result of every callable to a single SUCCESS or FAILURE value
->reduce(fn (int $results, int $result) => $results && $result, self::FAILURE);
}
protected function start(string $port): int
{
if ($pid = $this->getProcessID($port)) {
warning("[PID: $pid]: There's a server running already on port [$port]");

return self::FAILURE;
}

intro("Stating Google Chrome Driver on port [$port]");

$this->command('start', ['{port}' => $port]);

info('Google Chrome Driver server is up and running');

return self::SUCCESS;
}

public function stop(string $port): int
{
intro("Stopping Google Chrome Driver on port [$port]");

$pid = $this->getProcessID($port);

if (empty($pid)) {
warning("There's no server to stop on port [$port]");

return self::FAILURE;
}

$this->command('stop', ['{pid}' => $pid]);

info("Google Chrome Driver server stopped on port [$port]");

return self::SUCCESS;
}

protected function restart(string $port): int
{
intro("Restarting Google Chrome Driver on port [$port]");

$pid = $this->getProcessID($port);

if (empty($pid)) {
warning("There's no server to restart on port [$port]");

return self::FAILURE;
}

$this->command('stop', ['{pid}' => $pid]);

$this->command('start', ['{port}' => $port]);

info("Google Chrome Driver server restarted on port [$port]");

return self::SUCCESS;
}

protected function status(string $port): int
{
intro("Getting Google Chrome Driver status on port [$port]");

$pid = $this->getProcessID($port);

if (empty($pid)) {
warning("There's no server available on port [$port]");

return self::FAILURE;
}

$response = Http::get('http://localhost:9515/status');

$data = $response->json('value');

if (array_key_exists('error', $data) || ! $data['ready']) {
error('There was a problem, we cannot establish connection with the server');

return self::FAILURE;
}

info('Google Chrome server status: [OK]');

return self::SUCCESS;
}

protected function list(): int
{
info('Listing all the servers available');

$result = $this->getProcessIDs();

if (empty($result)) {
warning("There' no servers available to list");

return self::FAILURE;
}

$this->table(['PID', 'PORT'], $result);

return self::SUCCESS;
}

protected function kill(): int
{
$pids = $this->getProcessIDs();

if (empty($pids)) {
warning("There' no servers to kill");

return self::FAILURE;
}

$this->table(['PID', 'PORT'], $pids);

if (! $this->confirm('Are you sure you want to do this?')) {
return self::SUCCESS;
}

info('Stopping all the Google Chrome Driver servers that are available in the system');

$pids
->each(function (array $data) {
info("Stopping Google Chrome Driver [PID: {$data['pid']}]");

$this->command('stop', ['{pid}' => $data['pid']]);
});

return self::SUCCESS;
}

protected function command(string $cmd, array $with)
{
return Process::command(
Str::replace(
collect($with)->keys(),
collect($with)->values(),
$this->commands[$cmd]
)
)->path($this->getChromeDriverDirectory())->run();
}

protected function getProcessID(string $port): ?int
{
$process = $this->command('pid', ['{options}' => '--port='.$port]);

$output = explode(' ', trim($process->output()));

return (int) $output[0] ?: null;
}

protected function getProcessIDs(): ?Collection
{
$process = $this->command('pid', ['{options}' => '']);

if (empty($process->output())) {
return null;
}

$raw = explode("\n", trim($process->output()));

return collect($raw)->map(function (string $data) {
$data = explode(' ', $data);

return ['pid' => $data[0], 'port' => Str::remove('--port=', $data[1])];
});
}

protected function getPorts(): Collection
{
return collect($this->option('port') ? [...$this->option('port')] : $this->port)->unique()->filter();
}

protected function getChromeDriverDirectory(): string
{
return $this->option('path')
?? join_paths(
getenv('HOME'),
'.google-for-testing',
'chromedriver-'.$this->platforms[OperatingSystem::id()],
);
}
}
Loading

0 comments on commit c2b1d01

Please sign in to comment.