Skip to content

Commit

Permalink
Merge pull request #101 from tr33m4n/feature/add-send-later
Browse files Browse the repository at this point in the history
Add ability to send snapshots after a test suite has run
  • Loading branch information
tr33m4n authored May 3, 2022
2 parents 0980888 + e4a68f3 commit 9ed6b4d
Show file tree
Hide file tree
Showing 25 changed files with 800 additions and 582 deletions.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ composer require --dev tr33m4n/codeception-module-percy
```

## Example Configuration
The following example configuration assumes the `WebDriver` module has been configured correctly for your test suite
The following example `acceptance.suite.yml` configuration assumes the `WebDriver` module has been configured correctly for your test suite and
shows enabling the Percy module and setting some basic configuration:
```yaml
modules:
enabled:
Expand All @@ -27,26 +28,35 @@ modules:
- 320
minHeight: 1080
```
The following example shows how to configure the `percy:process-snapshots` in the `codeception.yml` file:
```yaml
extensions:
commands:
- Codeception\Module\Percy\Command\ProcessSnapshots
```

### Configuration Options
| 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 |
| Parameter | Type | Default | Description |
|------------------------------------|-----------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `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 |
| `collectOnly` | bool | `false` | Setting this to `true` will only collect snapshots, rather than collect and then send at the end of the run. They can then be sent manually by calling the `vendor/bin/codecept percy:process-snapshots` command |
| `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 |
| `snapshotServerPort` | int | `5338` | [debug] The port the Percy snapshot server will listen on |
| `throwOnAdapterError` | bool | `false` | [debug] Throw exception on adapter error |
| `instanceId` | string | `null` | [debug] An ID is used to differentiate between one Codeception runs output files to another, ensuring only the current runs output files are cleared on failure. Use this config to pass a custom instance ID |

## 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
### Overriding the `node` path
By default, the `node` executable used will be the one defined within the `PATH` of the user running the test suite. This can be overridden however, by setting the environment variable `PERCY_NODE_PATH` to your preferred location.
### Collect only
In some advanced CI setups, it might make sense to collect all snapshots for multiple runs with different parameters and then send them a single time when all runs are complete. This can be achieved by setting the `collectOnly` config to `true`. Once all runs are complete, running the command `vendor/bin/codecept percy:process-snapshots`
will then iterate all collected snapshots, send to Percy and then clean up the snapshot folder
### Example Test
```php
<?php
Expand Down
2 changes: 2 additions & 0 deletions codeception.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ actor_suffix: Tester
extensions:
enabled:
- Codeception\Extension\RunFailed
commands:
- Codeception\Module\Percy\Command\ProcessSnapshots
3 changes: 2 additions & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
RemoveUselessReturnTagRector::class,
ReturnTypeDeclarationRector::class => [
__DIR__ . '/src/Codeception/Module/Percy/Exchange/Adapter/CurlAdapter.php',
__DIR__ . '/src/Codeception/Module/Percy/RequestManagement.php'
__DIR__ . '/src/Codeception/Module/Percy/RequestManagement.php',
__DIR__ . '/src/Codeception/Module/Percy/Snapshot.php'
],
TypedPropertyRector::class => [
__DIR__ . '/src/Codeception/Module/Percy/Exchange/Adapter/CurlAdapter.php'
Expand Down
69 changes: 22 additions & 47 deletions src/Codeception/Module/Percy.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
use Codeception\Lib\ModuleContainer;
use Codeception\Module;
use Codeception\Module\Percy\ConfigManagement;
use Codeception\Module\Percy\CreateSnapshot;
use Codeception\Module\Percy\Exchange\Payload;
use Codeception\Module\Percy\Definitions;
use Codeception\Module\Percy\ProcessManagement;
use Codeception\Module\Percy\RequestManagement;
use Codeception\Module\Percy\ServiceContainer;
use Codeception\Module\Percy\SnapshotManagement;
use Codeception\TestInterface;
use Exception;
use Symfony\Component\Process\Exception\RuntimeException;
Expand All @@ -26,38 +25,16 @@
*/
class Percy extends Module
{
public const NAMESPACE = 'Percy';

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

/**
* @var array<string, mixed>
*/
protected $config = [
'snapshotBaseUrl' => 'http://localhost:5338',
'snapshotPath' => 'percy/snapshot',
'serializeConfig' => [
'enableJavaScript' => true
],
'snapshotConfig' => [
'widths' => [
375,
1280
],
'minHeight' => 1024
],
'snapshotServerTimeout' => null,
'throwOnAdapterError' => false,
'cleanSnapshotStorage' => false
];
protected $config = Definitions::DEFAULT_CONFIG;

private ConfigManagement $configManagement;

private RequestManagement $requestManagement;

private ProcessManagement $processManagement;

private CreateSnapshot $createSnapshot;
private SnapshotManagement $snapshotManagement;

private EnvironmentProviderInterface $environmentProvider;

Expand Down Expand Up @@ -85,20 +62,20 @@ public function __construct(
$serviceContainer = new ServiceContainer($webDriverModule, $percyModuleConfig);

$this->configManagement = $serviceContainer->getConfigManagement();
$this->requestManagement = $serviceContainer->getRequestManagement();
$this->processManagement = $serviceContainer->getProcessManagement();
$this->createSnapshot = $serviceContainer->getCreateSnapshot();
$this->snapshotManagement = $serviceContainer->getSnapshotManagement();
$this->environmentProvider = $serviceContainer->getEnvironmentProvider();
$this->webDriver = $webDriverModule;
}

/**
* Take snapshot of DOM and send to https://percy.io
*
* @throws \Codeception\Exception\ModuleException
* @throws \Codeception\Module\Percy\Exception\ConfigException
* @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
*/
Expand All @@ -114,18 +91,18 @@ public function takeAPercySnapshot(
// Add Percy CLI JS to page
$this->webDriver->executeJS($this->configManagement->getPercyCliBrowserJs());

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

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

Expand All @@ -138,19 +115,17 @@ public function takeAPercySnapshot(
*/
public function _afterSuite(): void
{
if (!$this->requestManagement->hasPayloads()) {
if ($this->configManagement->shouldCollectOnly()) {
$this->debugSection(Definitions::NAMESPACE, 'All snapshots collected!');

return;
}

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

try {
$this->requestManagement->sendRequest();
$this->snapshotManagement->sendInstance();
} catch (Exception $exception) {
$this->debugConnectionError($exception);
}

$this->debugSection(self::NAMESPACE, 'All snapshots sent!');
}

/**
Expand All @@ -164,7 +139,7 @@ public function _afterSuite(): void
*/
public function _failed(TestInterface $test, $fail): void
{
$this->requestManagement->resetRequest();
$this->snapshotManagement->resetInstance();
}

/**
Expand All @@ -176,7 +151,7 @@ public function _failed(TestInterface $test, $fail): void
private function debugConnectionError(Exception $exception): void
{
$this->debugSection(
self::NAMESPACE,
Definitions::NAMESPACE,
[$exception->getMessage(), $exception->getTraceAsString()]
);

Expand Down
35 changes: 0 additions & 35 deletions src/Codeception/Module/Percy/CleanSnapshots.php

This file was deleted.

79 changes: 79 additions & 0 deletions src/Codeception/Module/Percy/Command/ProcessSnapshots.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Codeception\Module\Percy\Command;

use Codeception\CustomCommandInterface;
use Codeception\Lib\Console\Output;
use Codeception\Module\Percy\Definitions;
use Codeception\Module\Percy\ServiceContainer;
use Codeception\Module\Percy\SnapshotManagement;
use Codeception\Util\Debug;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class ProcessSnapshots extends Command implements CustomCommandInterface
{
private SnapshotManagement $snapshotManagement;

/**
* ProcessSnapshots constructor.
*
* @param string|null $name
*/
public function __construct(
string $name = null
) {
$serviceContainer = new ServiceContainer(null, Definitions::DEFAULT_CONFIG);
$this->snapshotManagement = $serviceContainer->getSnapshotManagement();

parent::__construct($name);
}

/**
* @inheritDoc
*/
public static function getCommandName(): string
{
return 'percy:process-snapshots';
}

/**
* Get default description
*
* @return string
*/
public static function getDefaultDescription(): string
{
return 'Process any snapshots that exist in the snapshot directory, then cleanup';
}

/**
* {@inheritdoc}
*
* Process snapshots
*
* @throws \Codeception\Module\Percy\Exception\AdapterException
* @throws \Codeception\Module\Percy\Exception\ConfigException
* @throws \Codeception\Module\Percy\Exception\StorageException
* @throws \JsonException
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
Debug::setOutput(new Output([]));

$this->snapshotManagement->sendAll();
$this->snapshotManagement->resetAll();

$io->success('Successfully processed snapshots');

return self::SUCCESS;
}
}
Loading

0 comments on commit 9ed6b4d

Please sign in to comment.