Skip to content

Commit

Permalink
Merge pull request #2 from salsadigitalauorg/feature/use-traits
Browse files Browse the repository at this point in the history
Update to version 0.4.2: Introduced trait-based step definitions, upd…
  • Loading branch information
ivangrynenko authored Jan 20, 2025
2 parents d347913 + 129327f commit fe906eb
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 15 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,72 @@ Contributions to the **Scaffold Testing Library** are welcome! Please feel free
#### License
This library is provided under the MIT License. See the LICENSE file for more information.
# Breaking Changes in Version 0.4.2
## Important: Trait-based Step Definitions
Starting from version 0.4.2, we've moved to a trait-based approach for step definitions. This is a breaking change that requires manual intervention for existing projects.

### For Existing Projects

If you're upgrading from a previous version, you'll need to:

1. Remove the old step definitions from your `FeatureContext.php`
2. Add the trait to your `FeatureContext.php`:

```php
use Salsadigitalauorg\ScaffoldTesting\Traits\ScaffoldTestingTrait;
class FeatureContext implements Context
{
use ScaffoldTestingTrait;
// Your custom step definitions...
}
```

### For New Projects

The installer will automatically set up your `FeatureContext.php` with the trait. You can add your custom step definitions alongside the trait.

### Combining Custom Steps

You can add your own custom step definitions alongside the trait:

```php
class FeatureContext implements Context
{
use ScaffoldTestingTrait;
/**
* @Given I have my custom step
*/
public function iHaveMyCustomStep(): void
{
// Your custom step implementation
}
}
```

## Requirements

- PHP 8.3 or higher
- Composer 2.x
- Behat 3.13 or higher
- PHPUnit 9.6.13 or higher (compatible with Drupal core)

## Dependencies

This package requires the following dependencies which will be installed automatically:

```json
{
"require": {
"behat/behat": "^3.13",
"composer/composer": "^2.6",
"symfony/process": "^6.0|^7.0",
"phpunit/phpunit": "^9.6.13"
}
}
```
15 changes: 9 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
{
"name": "salsadigitalauorg/scaffold-testing",
"description": "Provides a set of default Behat tests for Drupal projects, ensuring consistent testing across deployments.",
"version": "0.4.1",
"version": "0.4.2",
"type": "library",
"require": {
"php": ">=8.3",
"behat/behat": "^3.13",
"composer/composer": "^2.6",
"symfony/process": "^6.0|^7.0",
"phpunit/phpunit": "^9.6.13"
},
"require-dev": {
"php": ">=8.2",
"behat/behat": "^3.6",
"drupal/drupal-extension": "^5.0",
"drevops/behat-format-progress-fail": "^1",
"drevops/behat-screenshot": "^1.5.0",
"drevops/behat-steps": "^2",
"phpunit/phpunit": "^9.5",
"composer/composer": "^2.0"
"drevops/behat-steps": "^2"
},
"autoload": {
"psr-4": {
Expand Down
130 changes: 121 additions & 9 deletions src/Installer/Installer.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
<?php

declare(strict_types=1);

namespace Salsadigitalauorg\ScaffoldTesting\Installer;

use Composer\Installer\LibraryInstaller;
use Composer\Script\Event;
use Composer\IO\IOInterface;
use Composer\Composer;

class Installer
/**
* Handles installation of Behat test files and context setup.
*/
class Installer extends LibraryInstaller
{
public static function features(Event $event)
protected $io;

public function __construct(IOInterface $io, Composer $composer, string $type = 'library')
{
parent::__construct($io, $composer, $type);
$this->io = $io;
}

/**
* Post install/update command hook.
*/
public static function features(Event $event): void
{
$io = $event->getIO();
$io->write('Installer::features method called');
$io->write('[scaffold-testing] Installer::features method called');

$composer = $event->getComposer();
$extras = $composer->getPackage()->getExtra();
Expand Down Expand Up @@ -59,7 +78,7 @@ public static function features(Event $event)
$io->writeError("Failed to update $file file in " . dirname($destPath));
}
} else {
$io->write("Skipped $file file as it already exists and override is set to false.");
$io->write("[scaffold-testing] Skipped $file file as it already exists and override is set to false.");
}
} else {
$io->writeError("Source file not found: $file");
Expand All @@ -72,22 +91,115 @@ public static function features(Event $event)
if (file_exists($contextSourcePath)) {
if (!file_exists($contextDestPath) || $override_feature_context) {
if (copy($contextSourcePath, $contextDestPath)) {
$io->write("Installed FeatureContext.php file to $bootstrapPath");
$io->write("[scaffold-testing] Installed FeatureContext.php file to $bootstrapPath");
} else {
$io->writeError("Failed to install FeatureContext.php file to $bootstrapPath");
$io->writeError("[scaffold-testing] Failed to install FeatureContext.php file to $bootstrapPath");
}
} else {
$io->write("Skipped FeatureContext.php file as it already exists and override is set to false.");
$io->write("[scaffold-testing] Skipped FeatureContext.php file as it already exists and override is set to false.");
}
} else {
$io->writeError("Source file not found: FeatureContext.php");
$io->writeError("[scaffold-testing] Source file not found: FeatureContext.php");
}

if (self::shouldUpdateFeatureContext($config)) {
self::updateFeatureContext($targetPath, $io);
}
}

private static function createDirectory($io, $path)
/**
* Creates necessary directories for test files.
*/
private static function createDirectory(IOInterface $io, string $path): void
{
if (!is_dir($path) && !mkdir($path, 0777, true)) {
$io->writeError("Failed to create directory: $path");
}
}

/**
* Updates or creates the FeatureContext file.
*/
private static function updateFeatureContext(string $projectRoot, IOInterface $io): void
{
$featureContextPath = $projectRoot . '/tests/behat/features/bootstrap/FeatureContext.php';
$useStatements = [
'use Behat\Behat\Context\Context;',
'use Behat\Behat\Hook\Scope\AfterScenarioScope;',
'use Behat\Behat\Hook\Scope\BeforeScenarioScope;',
'use Behat\Gherkin\Node\PyStringNode;',
'use Behat\Gherkin\Node\TableNode;',
'use PHPUnit\Framework\Assert;',
'use Symfony\Component\Process\Process;',
'use Salsadigitalauorg\ScaffoldTesting\Traits\ScaffoldTestingTrait;'
];

if (!file_exists($featureContextPath)) {
$template = self::getFeatureContextTemplate();
file_put_contents($featureContextPath, $template);
return;
}

$content = file_get_contents($featureContextPath);

// Add use statements if not present
foreach ($useStatements as $useStatement) {
if (strpos($content, $useStatement) === false) {
$content = preg_replace(
'/(namespace .*?;[\n\r]+)/s',
"$1\n" . $useStatement . "\n",
$content
);
}
}

// Add trait implementation if not present
if (strpos($content, 'use ScaffoldTestingTrait;') === false) {
$content = preg_replace(
'/(class FeatureContext implements Context[\n\r]*{)/s',
"$1\n use ScaffoldTestingTrait;\n",
$content
);
}

file_put_contents($featureContextPath, $content);
}

/**
* Gets the template for a new FeatureContext file.
*/
private static function getFeatureContextTemplate(): string
{
return <<<'PHP'
<?php
declare(strict_types=1);
namespace Salsadigitalauorg\ScaffoldTesting\Tests\Behat;
use Behat\Behat\Context\Context;
use Salsadigitalauorg\ScaffoldTesting\Traits\ScaffoldTestingTrait;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context
{
use ScaffoldTestingTrait;
/**
* Initializes context.
*/
public function __construct()
{
// Initialize your context here
}
}
PHP;
}

private static function shouldUpdateFeatureContext(array $config): bool
{
return isset($config['override_feature_context']) && $config['override_feature_context'] === true;
}
}
103 changes: 103 additions & 0 deletions src/Traits/ScaffoldTestingTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace Salsadigitalauorg\ScaffoldTesting\Traits;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
use Symfony\Component\Process\Process;
use Drupal\group\Entity\Group;
use Drupal\user\Entity\User;

/**
* Provides common step definitions for Behat tests.
*/
trait ScaffoldTestingTrait
{
protected Process $process;
protected ?User $user = null;

/**
* @BeforeScenario
*/
public function beforeScenario(BeforeScenarioScope $scope): void
{
$this->process = new Process(['php', '-S', 'localhost:8888']);
$this->process->start();
}

/**
* @AfterScenario
*/
public function afterScenario(AfterScenarioScope $scope): void
{
if (isset($this->process)) {
$this->process->stop();
}
}

/**
* @And I belong to group :groupName
*/
public function iBelongToGroup($groupName): void
{
if (!$this->user) {
throw new \Exception("No user is currently logged in. Please log in first.");
}

$groups = \Drupal::entityTypeManager()
->getStorage('group')
->loadByProperties(['label' => $groupName]);
$group = reset($groups);

if ($group) {
$group->addMember($this->user);
$group->save();
} else {
throw new \Exception("Group '$groupName' not found");
}
}

/**
* @Then I should be able to access the page
*/
public function iShouldBeAbleToAccessThePage(): void
{
$statusCode = $this->getSession()->getStatusCode();
if ($statusCode !== 200) {
$pageContent = $this->getSession()->getPage()->getContent();
throw new \Exception("Expected to access the page, but got status code: $statusCode. Page content: " . substr($pageContent, 0, 500));
}
}

/**
* @Then I should be denied access
*/
public function iShouldBeDeniedAccess(): void
{
$statusCode = $this->getSession()->getStatusCode();
if ($statusCode !== 403) {
$pageContent = $this->getSession()->getPage()->getContent();
throw new \Exception("Expected to be denied access (403), but got status code: $statusCode. Page content: " . substr($pageContent, 0, 500));
}
}

/**
* @Then I print all form fields
*/
public function iPrintAllFormFields(): void
{
$page = $this->getSession()->getPage();
$fields = $page->findAll('css', 'input, select, textarea');
foreach ($fields as $field) {
echo "Field: " . $field->getAttribute('name') . " | " . $field->getAttribute('id') . "\n";
}
}

// Add your step definitions here...
}

0 comments on commit fe906eb

Please sign in to comment.