Repository: Lakion/MinkDebugExtension Branch: master Commit: 270e5aa5aef5 Files: 14 Total size: 19.7 KB Directory structure: gitextract_rw74d7mo/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── features/ │ ├── bootstrap/ │ │ └── FeatureContext.php │ └── mink_debug.feature ├── src/ │ ├── Listener/ │ │ └── FailedStepListener.php │ └── ServiceContainer/ │ └── MinkDebugExtension.php └── test-application/ ├── behat.yml.dist ├── features/ │ └── test.feature └── logs/ └── .gitkeep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: pull_request: types: [opened, synchronize, edited, reopened] jobs: tests: runs-on: ubuntu-latest continue-on-error: false name: "PHP ${{ matrix.php }}" strategy: fail-fast: false matrix: php: - '7.4' - '8.0' - '8.1' steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: coverage: none ini-values: "memory_limit=-1" php-version: ${{ matrix.php }} tools: composer:v2 - name: Run Chrome Headless run: google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1 > /dev/null 2>&1 & - name: Get Composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache Composer uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json **/composer.lock') }} restore-keys: | ${{ runner.os }}-php-${{ matrix.php }}-composer- - name: Install PHP dependencies run: composer install --no-interaction - name: Validate composer.json run: composer validate --ansi --strict - name: Run Behat run: vendor/bin/behat --colors --strict --no-interaction -vvv -f progress ================================================ FILE: .gitignore ================================================ /vendor /composer.lock /behat.yml /test-application/logs/* !/test-application/logs/.gitkeep ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG ### v2.0.1 - [#35](https://github.com/FriendsOfBehat/MinkDebugExtension/issues/35) Ignore StreamReadException as well ([@pamil](https://github.com/pamil)) ### v2.0.0 * Added support for PHP 8.0 * Allowed taking screenshots with more drivers than just Selenium2Driver * Changed log files extension from `.log` to `.html` * Removed supplementary `upload-textfiles`, `upload-screenshots`, `wait-for-port` binaries * Renamed extension from `Lakion\Behat\MinkDebugExtension` to `FriendsOfBehat\MinkDebugExtension` ### v1.0.0 * Initial release. ================================================ FILE: LICENSE ================================================ Copyright (c) 2016-2020 Lakion 2020-2021 Kamil Kokot Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ MinkDebugExtension ================== **MinkDebugExtension** is a Behat extension made for debugging and logging Mink related data after every failed step. It is especially useful while running tests on continuous integration server like Travis. While using appropriate driver, you can also save screenshots just after the failure. Installation ------------ Assuming you already have Composer: ```bash composer require friends-of-behat/mink-debug-extension ``` Then you only need to configure your Behat profile: ```yml default: extensions: FriendsOfBehat\MinkDebugExtension: directory: directory-where-to-save-logs ``` Configuration reference ----------------------- Under `FriendsOfBehat\MinkDebugExtension` there are three options to be configured: - `directory` (required to enable extension) - contains path to directory that will contain generated logs. Use the variable `%paths.base%` to refer to the directory where your `behat.yml` is - `screenshot` (default `false`) - whether to save screenshots if using supporting driver - `clean_start` (default `true`) - whether to clean your existing logs on each Behat execution Testing ------- In order to test the extensions run: ```bash composer install bin/behat --strict ``` Authors ------- MinkDebugExtension was originally created by [Kamil Kokot](https://kamilkokot.com). See the list of [contributors](https://github.com/FriendsOfBehat/MinkDebugExtension/contributors). ================================================ FILE: UPGRADE.md ================================================ # UPGRADE ## FROM `1.x` TO `2.x` - Change required package from `lakion/mink-debug-extension` to `friends-of-behat/mink-debug-extension` in your `composer.json` - Change extension name from `Lakion\Behat\MinkDebugExtension` to `FriendsOfBehat\MinkDebugExtension` in your `behat.yml` - Make sure you're not using binaries provided by `1.x` version of this library ================================================ FILE: composer.json ================================================ { "name": "friends-of-behat/mink-debug-extension", "type": "behat-extension", "description": "Debug extension for Behat", "keywords": [ "debug", "behat", "mink", "logging" ], "homepage": "https://github.com/FriendsOfBehat/MinkDebugExtension", "license": "MIT", "authors": [ { "name": "Kamil Kokot", "email": "kamil@kokot.me", "homepage": "https://kamilkokot.com" } ], "require": { "php": ">=7.4", "behat/behat": "^3.5", "behat/mink-extension": "^2.3" }, "require-dev": { "behat/mink-goutte-driver": "^1.2", "behat/mink-selenium2-driver": "^1.4", "dmore/behat-chrome-extension": "^1.3", "dmore/chrome-mink-driver": "^2.7", "symfony/process": "^4.4 || ^5.2" }, "extra": { "branch-alias": { "dev-master": "2.1-dev" } }, "autoload": { "psr-4": { "FriendsOfBehat\\MinkDebugExtension\\": "src/" } } } ================================================ FILE: features/bootstrap/FeatureContext.php ================================================ */ private array $configuration = ['%clean_start%' => 'true']; /** @var string */ private string $testApplicationDir; /** * @BeforeScenario */ public function prepareProcess(): void { $phpFinder = new PhpExecutableFinder(); if (false === $php = $phpFinder->find()) { throw new \RuntimeException('Unable to find the PHP executable.'); } $this->phpBin = $php; $this->testApplicationDir = __DIR__ . '/../../test-application'; } /** * @Given there is following Behat extension configuration: */ public function thereIsBehatExtensionConfiguration(TableNode $table): void { foreach ($table->getRowsHash() as $key => $value) { $this->configuration['%' . $key . '%'] = $value; } } /** * @Given /configuration option "([^"]+?)" is set to "([^"]+?)"/ */ public function configurationOptionSet(string $key, string $value): void { $this->configuration['%' . $key . '%'] = $value; } /** * @When /I run Behat with failing scenarios(?: using (.+?) profile)?/ */ public function iRunBehat(?string $profile = null): void { $this->createBehatConfigurationFile(); $this->doRunBehat($this->getExtraConfiguration($profile)); $this->deleteBehatConfigurationFile(); } /** * @Then there should be text log generated */ public function thereShouldBeTextLogGenerated(): void { $logPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.html'; $logsAmount = count(glob($logPattern)); if ($logsAmount !== 1) { throw new \RuntimeException(sprintf('Expected 1 log file, found %d.', $logsAmount)); } } /** * @Then a screenshot should be made */ public function screenshotShouldBeMade(): void { $screenshotPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.png'; $screenshotsAmount = count(glob($screenshotPattern)); if ($screenshotsAmount !== 1) { throw new \RuntimeException(sprintf('Expected 1 screenshot, found %d.', $screenshotsAmount)); } } /** * @Then a screenshot should not be made */ public function screenshotShouldNotBeMade(): void { $screenshotPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.png'; $screenshotsAmount = count(glob($screenshotPattern)); if ($screenshotsAmount !== 0) { throw new \RuntimeException(sprintf('Expected no screenshots, found %d.', $screenshotsAmount)); } } private function createBehatConfigurationFile(): void { $behatConfiguration = strtr( file_get_contents($this->testApplicationDir . '/behat.yml.dist'), $this->configuration ); file_put_contents($this->testApplicationDir . '/behat.yml', $behatConfiguration); } private function getExtraConfiguration(?string $profile): array { if (null !== $profile) { return ['--profile=' . $profile]; } return []; } private function doRunBehat(array $extraConfiguration): void { $arguments = array_merge( [$this->phpBin, BEHAT_BIN_PATH, '--strict', '-vvv', '--no-interaction', '--lang=en'], $extraConfiguration ); $process = new Process($arguments, $this->testApplicationDir); $process->start(); $process->wait(); printf("stdOut:\n %s\nstdErr:\n%s\n", $process->getOutput(), $process->getErrorOutput()); } private function deleteBehatConfigurationFile(): void { if (file_exists($behatFile = $this->testApplicationDir . '/behat.yml')) { unlink($behatFile); } } } ================================================ FILE: features/mink_debug.feature ================================================ Feature: Logging debug data In order to debug my Behat suites with ease As a developer I want to be able to access logs Background: Given there is following Behat extension configuration: | directory | logs | | screenshot | true | Scenario: When I run Behat with failing scenarios Then there should be text log generated Scenario: When I run Behat with failing scenarios using javascript profile Then there should be text log generated And a screenshot should be made Scenario: Given configuration option "screenshot" is set to "false" When I run Behat with failing scenarios using javascript profile Then there should be text log generated And a screenshot should not be made ================================================ FILE: src/Listener/FailedStepListener.php ================================================ mink = $mink; $this->logDirectory = $logDirectory; $this->screenshot = $screenshot; } /** * @return array */ public static function getSubscribedEvents(): array { return [ StepTested::AFTER => ['logFailedStepInformations', -10], ]; } public function logFailedStepInformations(AfterStepTested $event): void { $testResult = $event->getTestResult(); if (!$testResult instanceof TestResult || TestResult::FAILED !== $testResult->getResultCode()) { return; } if (!$this->hasEligibleMinkSession()) { return; } $this->currentDateAsString = date('YmdHis'); $this->logPageContent(); if ($this->screenshot) { $this->logScreenshot(); } } private function logPageContent(): void { $session = $this->getSession(); $log = sprintf('Current page: %d %s', $this->getStatusCode($session), $this->getCurrentUrl($session)) . "\n"; $log .= $this->getResponseHeadersLogMessage($session); $log .= $this->getResponseContentLogMessage($session); $this->saveLog($log, 'html'); } private function logScreenshot(): void { $session = $this->getSession(); try { $this->saveLog($session->getScreenshot(), 'png'); } catch (UnsupportedDriverActionException | WebDriverException $exception) {} } private function saveLog(string $content, string $type): void { $path = sprintf("%s/behat-%s.%s", $this->logDirectory, $this->currentDateAsString, $type); if (file_put_contents($path, $content) === false) { throw new \RuntimeException(sprintf('Failed while trying to write log in "%s".', $path)); } } private function getSession(?string $name = null): Session { return $this->mink->getSession($name); } private function hasEligibleMinkSession(?string $name = null): bool { $name = $name ?: $this->mink->getDefaultSessionName(); return $this->mink->hasSession($name) && $this->mink->isSessionStarted($name); } private function getStatusCode(Session $session): ?int { try { return $session->getStatusCode(); } catch (MinkException | WebDriverException | StreamReadException $exception) { return null; } } private function getCurrentUrl(Session $session): ?string { try { return $session->getCurrentUrl(); } catch (MinkException | WebDriverException | StreamReadException $exception) { return null; } } private function getResponseHeadersLogMessage(Session $session): ?string { try { return 'Response headers:' . "\n" . print_r($session->getResponseHeaders(), true) . "\n"; } catch (MinkException | WebDriverException | StreamReadException $exception) { return null; } } private function getResponseContentLogMessage(Session $session): ?string { try { return 'Response content:' . "\n" . $session->getPage()->getContent() . "\n"; } catch (MinkException | WebDriverException | StreamReadException $exception) { return null; } } } ================================================ FILE: src/ServiceContainer/MinkDebugExtension.php ================================================ loadStepFailureListener($container); $this->removeAllExistingLogsIfRequested($config); $container->setParameter('mink_debug.directory', $config['directory']); $container->setParameter('mink_debug.screenshot', $config['screenshot']); $container->setParameter('mink_debug.clean_start', $config['clean_start']); } public function configure(ArrayNodeDefinition $builder): void { $builder ->children() ->scalarNode('directory')->isRequired()->end() ->booleanNode('screenshot')->defaultFalse()->end() ->booleanNode('clean_start')->defaultTrue()->end() ->end(); } public function getConfigKey(): string { return 'fob_mink_debug'; } public function initialize(ExtensionManager $extensionManager): void { } public function process(ContainerBuilder $container): void { } private function loadStepFailureListener(ContainerBuilder $container): void { $definition = new Definition(FailedStepListener::class, [ new Reference('mink'), '%mink_debug.directory%', '%mink_debug.screenshot%', ]); $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]); $container->setDefinition('mink_debug.listener.step_failure', $definition); } /** * @param array $config */ private function removeAllExistingLogsIfRequested(array $config): void { if ($config['clean_start']) { array_map('unlink', glob($config['directory'] . '/*.html')); array_map('unlink', glob($config['directory'] . '/*.png')); } } } ================================================ FILE: test-application/behat.yml.dist ================================================ default: suites: default: contexts: - Behat\MinkExtension\Context\MinkContext extensions: FriendsOfBehat\MinkDebugExtension: directory: %directory% clean_start: %clean_start% Behat\MinkExtension: sessions: default: goutte: ~ gherkin: filters: tags: "~@javascript" javascript: extensions: FriendsOfBehat\MinkDebugExtension: directory: %directory% screenshot: %screenshot% clean_start: %clean_start% DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~ Behat\MinkExtension: javascript_session: chrome sessions: chrome: chrome: api_url: http://127.0.0.1:9222 validate_certificate: false show_auto: false gherkin: filters: tags: "@javascript" ================================================ FILE: test-application/features/test.feature ================================================ Feature: Testing MinkDebugExtension In order to test MinkDebugExtension As a behat I want to download a page and fail Scenario: Downloading a page and failing When I go to "https://sylius.com" Then I select "Create failing test" from "Available steps" @javascript Scenario: Downloading a page and failing (Javascript session) When I go to "https://sylius.com" Then I select "Create failing test" from "Available steps" ================================================ FILE: test-application/logs/.gitkeep ================================================