Skip to content

Commit

Permalink
Merge pull request #100 from tr33m4n/feature/explicit-config-functions
Browse files Browse the repository at this point in the history
Make module more testable
  • Loading branch information
tr33m4n authored Apr 29, 2022
2 parents 434839c + 0c25894 commit 0980888
Show file tree
Hide file tree
Showing 22 changed files with 724 additions and 443 deletions.
55 changes: 13 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,6 @@
Percy https://percy.io module for Codeception

## Requirements
### `v1.1.x`
- Node.js `>=10.0.0`
- PHP `>= 7.2`
- Composer `v1`

### `v2.0.x`
- Node.js `>=10.0.0`
- PHP `>= 7.3`
- Composer `v2`

### `v3.0.x`
- Node.js `>=12.0.0`
- PHP `>= 7.3`
- Composer `v2`

### `v4.0.x`
- Node.js `>=14.0.0`
- PHP `>= 7.4`
- Composer `v2`
Expand All @@ -27,19 +11,6 @@ Percy https://percy.io module for Codeception
composer require --dev tr33m4n/codeception-module-percy
```

## Upgrading
### `v1.0.x` to `v1.1.x`
The way in which the Percy agent is started and stopped in `v1.1.x` changes significantly from `v1.0.x`. You no longer need to prefix your Codeception run command with `npx percy exec --` :tada:

### `v1.1.x` to `v2.0.x`
`v2.0.x` only supports PHP `7.3` and composer `v2` or later, however the base functionality is the same as `v1.1.x`

### `v2.0.x` to `v3.0.x`
`v3.0.x` only supports Node `>=12`. Due to a typical PHP based platform using Composer not blocking the installation of this version if you have a lesser Node version, caution is advised!

### `v3.0.x` to `v4.0.x`
`v4.0.x` only support Node `>=14` and PHP `>=7.4`. The `driver` config parameter has also been dropped as this has always explicitly required the Codeception WebDriver module

## Example Configuration
The following example configuration assumes the `WebDriver` module has been configured correctly for your test suite
```yaml
Expand All @@ -58,19 +29,19 @@ modules:
```
### Configuration Options
| Parameter | Type | Default | Description |
| --------------------------------- | -------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `snapshotEndpoint` | string | `http://localhost:5338` | The endpoint used for operations within the Percy agent |
| `snapshotPath` | string | `percy/snapshot` | The path relative to the agent endpoint to post a snapshot to |
| `snapshotConfig` | object | `{}` | Additional configuration to pass to the "snapshot" functionality |
| `snapshotConfig.percyCSS` | string | `null` | Percy specific CSS to apply to the "snapshot" |
| `snapshotConfig.minHeight` | int | `null` | Minimum height of the resulting "snapshot" in pixels |
| `snapshotConfig.enableJavaScript` | bool | `false` | Enable JavaScript in the Percy rendering environment |
| `snapshotConfig.widths` | array | `null` | An array of integers representing the browser widths at which you want to take snapshots |
| `serializeConfig` | object | `{"enableJavaScript": true}` | Additional configuration to pass to the `PercyDOM.serialize` method injected into the web driver DOM |
| `snapshotServerTimeout` | int\|null | `null` | [debug] The length of the time the Percy snapshot server will listen for incoming snapshots and send on to Percy.io (the amount of time needed to send all snapshots after a successful test suite run). No timeout is set by default |
| `throwOnAdapterError` | bool | `false` | [debug] Throw exception on adapter error |
| `cleanSnapshotStorage` | bool | `false` | [debug] Clean stored snapshot HTML after run |
| Parameter | Type | Default | Description |
|------------------------------------|------------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `snapshotBaseUrl` | string | `http://localhost:5338` | The base URL used for operations within the Percy agent |
| `snapshotPath` | string | `percy/snapshot` | The path relative to the agent endpoint to post a snapshot to |
| `snapshotConfig` | object | `{}` | Additional configuration to pass to the "snapshot" functionality |
| `snapshotConfig.percyCSS` | string | `null` | Percy specific CSS to apply to the "snapshot" |
| `snapshotConfig.minHeight` | int | `null` | Minimum height of the resulting "snapshot" in pixels |
| `snapshotConfig.enableJavaScript` | bool | `false` | Enable JavaScript in the Percy rendering environment |
| `snapshotConfig.widths` | array | `null` | An array of integers representing the browser widths at which you want to take snapshots |
| `serializeConfig` | object | `{"enableJavaScript": true}` | Additional configuration to pass to the `PercyDOM.serialize` method injected into the web driver DOM |
| `snapshotServerTimeout` | int\ | `null` | [debug] The length of the time the Percy snapshot server will listen for incoming snapshots and send on to Percy.io (the amount of time needed to send all snapshots after a successful test suite run). No timeout is set by default |
| `throwOnAdapterError` | bool | `false` | [debug] Throw exception on adapter error |
| `cleanSnapshotStorage` | bool | `false` | [debug] Clean stored snapshot HTML after run |

## Running
The Percy integration runs automatically with the test suite but will need your `PERCY_TOKEN` to be set to successfully send snapshots. For more information, see https://docs.percy.io/docs/environment-variables#section-required
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"codeception/module-webdriver": "^2.0",
"eloquent/composer-npm-bridge": "^5.0",
"ramsey/uuid": "^4.1",
"symfony/process": "^5.2"
"symfony/process": "^5.2",
"tr33m4n/codeception-module-percy-environment": "^1.0"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ parameters:
bootstrapFiles:
- vendor/codeception/codeception/autoload.php
ignoreErrors:
- '#Property Codeception\\Module\\Percy::\$webDriver#'
- '#of function (.*) expects CurlHandle, resource given.#'
- '#\$resource \(resource\|false\) does not accept CurlHandle.#'
- '#Method Codeception\\Module\\Percy\\ServiceContainer::(.*) should return (.*) but returns mixed.#'
3 changes: 2 additions & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
RemoveUselessParamTagRector::class,
RemoveUselessReturnTagRector::class,
ReturnTypeDeclarationRector::class => [
__DIR__ . '/src/Codeception/Module/Percy/Exchange/Adapter/CurlAdapter.php'
__DIR__ . '/src/Codeception/Module/Percy/Exchange/Adapter/CurlAdapter.php',
__DIR__ . '/src/Codeception/Module/Percy/RequestManagement.php'
],
TypedPropertyRector::class => [
__DIR__ . '/src/Codeception/Module/Percy/Exchange/Adapter/CurlAdapter.php'
Expand Down
104 changes: 55 additions & 49 deletions src/Codeception/Module/Percy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

namespace Codeception\Module;

use Codeception\Lib\ModuleContainer;
use Codeception\Module;
use Codeception\Module\Percy\ConfigProvider;
use Codeception\Module\Percy\Exception\ApplicationException;
use Codeception\Module\Percy\ConfigManagement;
use Codeception\Module\Percy\CreateSnapshot;
use Codeception\Module\Percy\Exchange\Payload;
use Codeception\Module\Percy\FilepathResolver;
use Codeception\Module\Percy\InfoProvider;
use Codeception\Module\Percy\ProcessManagement;
use Codeception\Module\Percy\RequestManagement;
use Codeception\Module\Percy\ServiceContainer;
use Codeception\TestInterface;
use Exception;
use Symfony\Component\Process\Exception\RuntimeException;
use tr33m4n\CodeceptionModulePercyEnvironment\EnvironmentProviderInterface;

/**
* Class Percy
Expand All @@ -27,6 +28,8 @@ class Percy extends Module
{
public const NAMESPACE = 'Percy';

public const PACKAGE_NAME = 'tr33m4n/codeception-module-percy';

/**
* @var array<string, mixed>
*/
Expand All @@ -48,78 +51,81 @@ class Percy extends Module
'cleanSnapshotStorage' => false
];

private ?WebDriver $webDriver = null;
private ConfigManagement $configManagement;

private RequestManagement $requestManagement;

private ProcessManagement $processManagement;

private ?string $percyCliJs = null;
private CreateSnapshot $createSnapshot;

private EnvironmentProviderInterface $environmentProvider;

private WebDriver $webDriver;

/**
* {@inheritdoc}
* Percy constructor.
*
* @throws \Exception
* @throws \Codeception\Exception\ModuleException
* @param array<string, mixed>|null $config
* @param \Codeception\Lib\ModuleContainer $moduleContainer
*/
public function _initialize(): void
{
/** @var array<string, mixed> $moduleConfig */
$moduleConfig = $this->_getConfig() ?? [];
ConfigProvider::set($moduleConfig);
public function __construct(
ModuleContainer $moduleContainer,
array $config = null
) {
parent::__construct($moduleContainer, $config);

/** @var \Codeception\Module\WebDriver $webDriverModule */
$webDriverModule = $this->getModule('WebDriver');
if (!$webDriverModule instanceof WebDriver) {
throw new ApplicationException('"WebDriver" module not found');
}

/** @var array<string, mixed> $percyModuleConfig */
$percyModuleConfig = $this->_getConfig() ?? [];

$serviceContainer = new ServiceContainer($webDriverModule, $percyModuleConfig);

$this->configManagement = $serviceContainer->getConfigManagement();
$this->requestManagement = $serviceContainer->getRequestManagement();
$this->processManagement = $serviceContainer->getProcessManagement();
$this->createSnapshot = $serviceContainer->getCreateSnapshot();
$this->environmentProvider = $serviceContainer->getEnvironmentProvider();
$this->webDriver = $webDriverModule;
$this->percyCliJs = file_get_contents(FilepathResolver::percyCliBrowserJs()) ?: null;
}

/**
* Take snapshot of DOM and send to https://percy.io
*
* @throws \Codeception\Module\Percy\Exception\StorageException
* @throws \JsonException
* @throws \tr33m4n\CodeceptionModulePercyEnvironment\Exception\EnvironmentException
* @throws \Codeception\Module\Percy\Exception\ConfigException
* @param string $name
* @param array<string, mixed> $snapshotConfig
*/
public function takeAPercySnapshot(
string $name,
array $snapshotConfig = []
): void {
// If we cannot access the CLI JS, return silently
if (!$this->percyCliJs) {
return;
}

// If web driver has not been set, return
if (null === $this->webDriver) {
return;
}

// If remote web driver has not been set, return
// If the remote web driver doesn't exist, return
if (null === $this->webDriver->webDriver) {
return;
}

// Add Percy CLI JS to page
$this->webDriver->executeJS($this->percyCliJs);

/** @var array<string, mixed> $moduleSnapshotConfig */
$moduleSnapshotConfig = $this->_getConfig('snapshotConfig') ?? [];
$this->webDriver->executeJS($this->configManagement->getPercyCliBrowserJs());

/** @var string $domSnapshot */
$domSnapshot = $this->webDriver->executeJS(
sprintf(
'return PercyDOM.serialize(%s)',
json_encode($this->_getConfig('serializeConfig'), JSON_THROW_ON_ERROR)
)
sprintf('return PercyDOM.serialize(%s)', $this->configManagement->getSerializeConfig())
);

RequestManagement::addPayload(
Payload::from(array_merge($moduleSnapshotConfig, $snapshotConfig))
$this->requestManagement->addPayload(
Payload::from(array_merge($this->configManagement->getSnapshotConfig(), $snapshotConfig))
->withName($name)
->withUrl($this->webDriver->webDriver->getCurrentURL())
->withDomSnapshot($domSnapshot)
->withClientInfo(InfoProvider::getClientInfo())
->withEnvironmentInfo(InfoProvider::getEnvironmentInfo($this->webDriver->webDriver))
->withDomSnapshot($this->createSnapshot->execute($domSnapshot))
->withClientInfo($this->environmentProvider->getClientInfo())
->withEnvironmentInfo($this->environmentProvider->getEnvironmentInfo())
);
}

Expand All @@ -132,14 +138,14 @@ public function takeAPercySnapshot(
*/
public function _afterSuite(): void
{
if (!RequestManagement::hasPayloads()) {
if (!$this->requestManagement->hasPayloads()) {
return;
}

$this->debugSection(self::NAMESPACE, 'Sending Percy snapshots..');

try {
RequestManagement::sendRequest();
$this->requestManagement->sendRequest();
} catch (Exception $exception) {
$this->debugConnectionError($exception);
}
Expand All @@ -158,7 +164,7 @@ public function _afterSuite(): void
*/
public function _failed(TestInterface $test, $fail): void
{
RequestManagement::resetRequest();
$this->requestManagement->resetRequest();
}

/**
Expand All @@ -174,16 +180,16 @@ private function debugConnectionError(Exception $exception): void
[$exception->getMessage(), $exception->getTraceAsString()]
);

if (!$this->_getConfig('throwOnAdapterError')) {
return;
}

try {
ProcessManagement::stopPercySnapshotServer();
$this->processManagement->stopPercySnapshotServer();
} catch (RuntimeException $exception) {
// Fail silently if the process is not running
}

if (!$this->configManagement->shouldThrowOnAdapterError()) {
return;
}

throw $exception;
}
}
35 changes: 35 additions & 0 deletions src/Codeception/Module/Percy/CleanSnapshots.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Codeception\Module\Percy;

class CleanSnapshots
{
private ConfigManagement $configManagement;

/**
* SnapshotManagement constructor.
*
* @param \Codeception\Module\Percy\ConfigManagement $configManagement
*/
public function __construct(
ConfigManagement $configManagement
) {
$this->configManagement = $configManagement;
}

/**
* Clean snapshot directory
*/
public function execute(): void
{
if (!$this->configManagement->shouldCleanSnapshotStorage()) {
return;
}

foreach (glob(codecept_output_dir(sprintf(CreateSnapshot::OUTPUT_FILE_PATTERN, '*'))) ?: [] as $snapshotFile) {
unlink($snapshotFile);
}
}
}
Loading

0 comments on commit 0980888

Please sign in to comment.