Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: enhance test runs #56

Merged
merged 6 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:

- name: Run tests
env:
BUILD_MATRIX: PHP ${{ matrix.php }}
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Desktop.ini
.env.*
.env
.php-version
.runs
.tool-versions
/*.session.sql
coveralls-upload.json
Expand Down
266 changes: 266 additions & 0 deletions src/BrowserStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<?php

namespace Creasi\DuskBrowserStack;

use Illuminate\Support\Collection;
use Laravel\Dusk\Browser;

final class BrowserStack
{
/**
* The build name cache.
*/
private static ?string $buildName = null;

/**
* The project name cache.
*/
private static ?string $projectName = null;

/**
* The local identifier cache.
*/
private static ?string $localIdentifier = null;

/**
* The run number cache.
*/
private static ?int $runNumber = null;

/**
* @param Collection<int, Browser> $browsers
*/
public function __construct(
private Collection $browsers
) {
// .
}

/**
* Execute javascript command on BrowserStack.
*
* @link https://www.browserstack.com/docs/automate/selenium/js-executors
*/
public function executeCommand(string $action, ?array $arguments = null): mixed
{
$command = \array_filter([
'action' => $action,
'arguments' => $arguments,
]);

return $this->browsers->each(
fn (Browser $browser) => $browser->driver->executeScript('browserstack_executor: '.\json_encode($command))
);
}

/**
* Set session status on BrowserStack.
*
* @link https://www.browserstack.com/docs/automate/selenium/js-executors
*/
public function setSessionStatus(string $status, string $reason): void
{
$this->executeCommand('setSessionStatus', [
'status' => $status,
'reason' => $reason,
]);
}

/**
* Retreive session details from BrowserStack.
*
* @link https://www.browserstack.com/docs/automate/selenium/js-executors
*/
public function getSessionDetails()
{
return $this->executeCommand('getSessionDetails');
}

/**
* Initiate a BrowserStackLocal process.
*/
public static function createLocalProcess(array $arguments = []): LocalProcess
{
return new LocalProcess(\array_merge($arguments, [
'key' => self::getAccessKey(),
'local-identifier' => self::getLocalIdentifier(),
]));
}

/**
* Check whether the BrowserStack AccessKey is set.
*/
public static function hasAccessKey(): bool
{
return env('BROWSERSTACK_ACCESS_KEY') !== null;
}

/**
* Get the BrowserStack AccessKey.
*/
public static function getAccessKey(): ?string
{
return env('BROWSERSTACK_ACCESS_KEY');
}

/**
* Get the Local Identifier.
*/
public static function getLocalIdentifier(): string
{
if (self::$localIdentifier) {
return self::$localIdentifier;
}

if ($project = env('BROWSERSTACK_LOCAL_IDENTIFIER')) {
return $project;
}

$run = self::getRunsNumber() ?: null;
$sha = \trim(self::getCommitSha().'-'.$run, '- ');

return self::$localIdentifier = self::getProjectName().'_'.$sha;
}

/**
* Get the Build Name.
*/
public static function getBuildName(): string
{
if (self::$buildName) {
return self::$buildName;
}

$build = env('BROWSERSTACK_BUILD_NAME');

if ($build && (\strlen($build) > 0 && \strlen($build) <= 255)) {
return self::$buildName = $build;
}

$numbers = '';
$branch = env('GITHUB_HEAD_REF', \exec('git branch --show-current'));
$message = self::getRunsMessage().' '.self::getCommitSha();

if ($buildMatrix = env('BUILD_MATRIX')) {
$numbers .= \sprintf(', Matrix: %s', $buildMatrix);
}

if ($runNumber = self::getRunsNumber()) {
$numbers .= \sprintf(', Run: %d', $runNumber);
}

return self::$buildName = \sprintf('[%s] %s%s', $branch, $message, $numbers);
}

/**
* Get the Project Name.
*/
public static function getProjectName(): string
{
if (self::$projectName) {
return self::$projectName;
}

if ($project = env('BROWSERSTACK_PROJECT_NAME')) {
return $project;
}

if ($project = \env('GITHUB_REPOSITORY')) {
return \explode('/', $project)[1];
}

return self::$projectName = \substr(\explode('/', \exec('git remote get-url origin'))[1], 0, -4);
}

/**
* Get the Driver URL.
*/
public static function getDriverURL(): string
{
if (! self::hasAccessKey()) {
return env('DUSK_DRIVER_URL', 'http://localhost:9515');
}

$username = env('BROWSERSTACK_USERNAME');

return 'https://'.$username.':'.self::getAccessKey().'@hub.browserstack.com/wd/hub';
}

/**
* Check wheter the current branch is dirty.
*/
private static function isDirty(): bool
{
return (bool) \exec('[[ -n `git status --porcelain` ]] && echo 1');
}

/**
* Get local runs number.
*/
private static function getRunsNumber(): ?int
{
$runPath = \dirname(__DIR__).'/.runs';

// When it runs on github actions, use its run number instead.
if ($runNumber = \env('GITHUB_RUN_ATTEMPT')) {
return (int) $runNumber;
}

// Check whether the current branch is dirty.
if (! self::isDirty()) {
// If there's any runs file, it must be from previous run.
if (! \file_exists($runPath)) {
// Get rid of it!
\unlink($runPath);
}

// Don't do anything.
return null;
}

// Please do cache.
if (self::$runNumber) {
return self::$runNumber;
}

// Create new runs file if it doesn't exist.
if (! \file_exists($runPath)) {
\file_put_contents($runPath, '0');
}

// Get the run number, which is must be from previous run.
// Increment the run number and save it back to the file.
$runNumber = (int) \file_get_contents($runPath);
\file_put_contents($runPath, self::$runNumber = ++$runNumber);

return self::$runNumber;
}

/**
* Get run message.
*/
private static function getRunsMessage(): string
{
if (\env('GITHUB_EVENT_NAME') === 'pull_request') {
return \sprintf('PR #%s', \explode('/', \env('GITHUB_REF'))[2]);
}

if (self::isDirty()) {
return 'uncommited changes';
}

return \exec('git log -1 --pretty=%s');
}

/**
* Get commit sha in short format.
*/
private static function getCommitSha(): string
{
if ($githubSha = \env('GITHUB_SHA')) {
return \substr($githubSha, 0, 7);
}

return \exec('git rev-parse --short HEAD');
}
}
21 changes: 19 additions & 2 deletions src/LocalBinary.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
class LocalBinary
{
/**
* @var string Path to the bin directory.
* Path to the bin directory.
*/
private static string $directory = __DIR__.'/../bin';

/**
* Path to the BrowserStackLocal binary.
*/
private static ?string $path = null;

/**
* Set the path to the custom BrowserStackLocal binary.
*/
public static function use(string $path): void
{
static::$path = $path;
}

/**
* Retrieve the download URL for the BrowserStackLocal binary.
*/
Expand All @@ -24,13 +37,17 @@ public static function getDownloadUrl(): string
*/
public static function getPath(): string
{
if (self::$path) {
return self::$path;
}

$os = static::getPlatform();

if ($os === 'win32') {
$os .= '.exe';
}

return self::$directory.'/bs-local-'.$os;
return self::getDirectory().'/bs-local-'.$os;
}

/**
Expand Down
12 changes: 9 additions & 3 deletions src/LocalProcess.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ class LocalProcess
*/
protected ?Process $process = null;

public function __construct(?string $binary, array $arguments = [])
public function __construct(array $arguments = [])
{
$binary = \realpath($binary ?? LocalBinary::getPath());
$binary = LocalBinary::getPath();

if (! $binary) {
if (! \realpath($binary)) {
throw new \RuntimeException("Unable to locate the BrowserStackLocal binary: {$binary}");
}

Expand All @@ -36,6 +36,9 @@ public function __construct(?string $binary, array $arguments = [])
}
}

/**
* Start the browserstack-local process.
*/
public function start(): void
{
$this->process = new Process($this->commands);
Expand Down Expand Up @@ -82,6 +85,9 @@ public function stop(): void
$this->process->stop();
}

/**
* Check whether browserstack-local process is running.
*/
public function isRunning(): bool
{
return $this->process?->isRunning() ?? false;
Expand Down
Loading