diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7a3d215 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +/.github export-ignore +/docs export-ignore +/tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +CONTRIBUTING.md export-ignore +.readthedocs.yaml export-ignore +phpunit.xml.dist export-ignore diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..2f0e2b7 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,34 @@ +name: Docs + +on: + push: + paths: ['docs/**', '.github/workflows/docs.yml', '.readthedocs.yaml'] + pull_request: + paths: ['docs/**', '.github/workflows/docs.yml', '.readthedocs.yaml'] + +defaults: + run: + shell: bash + +jobs: + + tests: + name: Documentation + runs-on: Ubuntu-20.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup Python + uses: actions/setup-python@v5 + + - name: Install dependencies + run: | + pip install Sphinx sphinx_rtd_theme + + - name: Run tests + run: | + sphinx-build -nW -b html -d docs/build/doctrees docs docs/build/html diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..59d3d8d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,103 @@ +name: CI + +on: + push: + paths-ignore: ['docs/**', '.github/workflows/docs.yml', '.readthedocs.yaml'] + pull_request: + paths-ignore: ['docs/**', '.github/workflows/docs.yml', '.readthedocs.yaml'] + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + + tests: + name: "PHP ${{ matrix.php }}${{ matrix.with_coverage == true && ' with coverage' || ''}}" + runs-on: ubuntu-20.04 + strategy: + matrix: + php: [ '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2', '8.3' ] + with_coverage: [ false ] + include: + - php: '7.4' + with_coverage: true + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + coverage: "xdebug" + php-version: "${{ matrix.php }}" + tools: composer + # PHP 7.1 development web server segfaults if timezone not set. + ini-values: date.timezone=Europe/Paris, error_reporting=-1, display_errors=On + + - name: Configure for PHP >= 7.1 + if: "${{ matrix.php >= '7.1' }}" + run: | + composer require --no-update --dev symfony/error-handler "^4.4 || ^5.0" + + - name: Configure for PHP >= 8.0 + if: "${{ matrix.php >= '8.0' }}" + run: | + composer require --no-update --dev scrutinizer/ocular + + - name: Install dependencies + uses: "ramsey/composer-install@v3" + with: + dependency-versions: "highest" + + - name: Run tests with Coverage + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + BS_USERNAME: ${{ secrets.BS_USERNAME }} + BS_ACCESS_KEY: ${{ secrets.BS_ACCESS_KEY }} + if: "${{ matrix.with_coverage == true }}" + run: | + vendor/bin/phpunit -v --coverage-clover=coverage.clover --log-junit junit.xml + + - name: Run tests without Coverage + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + BS_USERNAME: ${{ secrets.BS_USERNAME }} + BS_ACCESS_KEY: ${{ secrets.BS_ACCESS_KEY }} + if: "${{ matrix.with_coverage == false }}" + run: | + vendor/bin/phpunit -v + + - name: Upload coverage to Codecov + if: ${{ matrix.with_coverage == true && !cancelled() }} + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ matrix.with_coverage == true && !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload Coverage to Scrutinizer CI (PHP < 8.0) + if: "${{ matrix.php < '8.0' && matrix.with_coverage == true }}" + run: | + wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --repository=g/minkphp/phpunit-mink --format=php-clover coverage.clover + + - name: Upload Coverage to Scrutinizer CI (PHP >= 8.0) + if: "${{ matrix.php >= '8.0' && matrix.with_coverage == true }}" + run: | + vendor/bin/ocular code-coverage:upload --repository=g/minkphp/phpunit-mink --format=php-clover coverage.clover diff --git a/.gitignore b/.gitignore index 592e632..ac65311 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ vendor composer.phar -composer.lock phpunit.xml +.phpunit.result.cache build diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..c789532 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +python: + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b645438..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - -before_script: - - curl http://getcomposer.org/installer | php - - php composer.phar require satooshi/php-coveralls:dev-master --dev --prefer-source - -script: - - mkdir -p build/logs - - phpunit -v --coverage-clover build/logs/clover.xml - -after_script: - - php vendor/bin/coveralls -v \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..405f87f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,133 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] +### Added +... + +### Changed +... + +### Fixed +... + +## [2.5.0] - 2024-11-30 +### Added +- Added support for WebdriverClassicDriver Mink's Driver (supports Selenium 2, 3, 4) as `webdriver-classic`. + +## [2.4.0] - 2024-07-15 +### Added +- Specify failed PHPUnit assertion text to the BrowserStack ("reason" field)/SauceLabs("custom-data" field) test. +- Added the `$auto_create` parameter to the `BrowserTestCase::getSession` method, which allows to verify if session is already started. +- Added the `ISessionStrategy::isFreshSession` method to indicate fact, that previous `ISessionStrategy::session` call has created a new session instead of reusing a previously created one. Can be used to perform a login once per a test case class. + +### Changed +- Bumped minimum PHP version to 5.6. +- Changed default OS from "Windows 7" to "Windows 10" for BrowserStack/SauceLabs browser configurations. +- Allow using self-signed/invalid SSL certificates during testing on the SauceLabs by default. +- Rewritten library object communication mechanism (the event dispatcher is no longer used). Update any custom session strategy/browser configuration implementations. +- Reduce memory consumption by rewriting `SessionStrategyFactory` and `SessionStrategyManager` classes. +- (Not a BC break) Some public methods of the `BrowserTestCase` class are protected now. Affected methods: `setRemoteCoverageScriptUrl`, `setBrowser`, `getBrowser`, `setSessionStrategy`, `getSessionStrategy`, `getCollectCodeCoverageInformation`, `getRemoteCodeCoverageInformation`. +- (Not a BC break) Some protected properties of the `BrowserTestCase` class are private now. Affected properties: `sessionStrategyManager`, `remoteCoverageHelper`, `sessionStrategy`. +- Bumped minimal required `Behat/Mink` version to 1.8 (needed after `SessionProxy` class removal). +- Shared session strategy now also closes popups left over from the previous test before switching back to the main window. +- Bumped minimal required `console-helpers/phpunit-compat` version to 1.0.3 to load classes through the custom autoloader script. + +### Fixed +- The remote code coverage collection cookies were set even, when the remote code coverage script URL wasn't specified. +- The `BrowserTestCase::onTestSuiteEnded` method was called for tests, excluded through the `--filter` option of the PHPUnit. + +## [2.3.0] - 2022-11-24 +### Changed +- Bumped minimum PHPUnit version to 4.8.35 or 5.4.3. +- Added support for PHPUnit 6.x, PHPUnit 7.x, PHPUnit 8.x and PHPUnit 9.x versions. +- Use namespaced class versions of PHPUnit. +- Bumped minimum PHP version to 5.4.7. +- Test case configuration method renamed from "BrowserTestCase::setUp" into "BrowserTestCase::setUpTest". + +### Fixed +- Fixed "PHP Strict standards" notice when used with PHPUnit 5+. +- Fixed issue with BrowserStack, that caused PHPUnit test result not being reported into the BrowserStack due their API changes. + +## [2.2.0] - 2016-06-26 +### Added +- Added support for Guzzle 6 in `goutte` driver. + +### Changed +- Start sessions only, when somebody requests them. +- Allow using PHPUnit 4.x or PHPUnit 5.x and Symfony 3.0. +- Dependency on Pimple is removed, which allowed library to be used on projects using any of Pimple version (even 1.0) themselves. + +### Fixed +... + +## [2.1.1] - 2015-08-01 +### Fixed +- Session sharing (for tests in same test case) wasn't working when test suite consisted from tests using both session strategies (isolated and shared). + +## [2.1.0] - 2015-05-06 +### Added +- Complete integration with BrowserStack (includes tunnel creation). +- Tunnel identifier can be specified through `PHPUNIT_MINK_TUNNEL_ID`, when for Sauce Labs or BrowserStack browser configurations are used. +- Allow specifying Mink driver to use within browser configuration (the new `driver` parameter). + +### Changed +- Allow library to be used in projects with either Pimple 2.x or Pimple 3.x installed. +- The tunnel isn't automatically created, when using Sauce Labs or BrowserStack browser configuration and running test suite on Travis CI. + +### Fixed +- The Sauce Labs and BrowserStack unit tests were executed even, when their credentials weren't specified in `phpunit.xml` (affects contributors only). + +## [2.0.1] - 2014-11-27 +### Changed +- Remote code coverage collection is now disabled by default. + +### Fixed +- Attempt to use `@dataProvider` annotation ended up in exception. + +## [2.0.0] - 2014-08-09 +### Added +- Added support for using custom browser configuration (the `BrowserConfigurationFactory::register` method). +- Adding BrowserStack testing service support (experimental). +- Allow running testing using "Sauce Labs" and "BrowserStack" on Travis CI. +- Allow using SauceConnect (secure tunnel creation to the Sauce Labs servers) on Travis CI, when Sauce Labs browser configuration is used. +- Added support for HHVM. + +### Changed +- Changed default OS for Sauce Labs/BrowserStack from "Windows XP" to "Windows 7". + +### Fixed +- Sessions were stopped prematurely, when test suite consisted of tests with different session strategies (e.g. one isolated and one shared). + +## [1.1.0] - 2014-03-22 +### Added +- Added `BrowserTestCase::createBrowserConfiguration` method for creating instance of browser configuration class based on given parameters. + +### Changed +- Use DIC (dependency injection container) to organize interactions between library modules. +- When unknown parameters are specified during browser configuration creation an exception is thrown. +- The `SauceLabsBrowserConfiguration` class now would throw an exception, when supplied driver instance isn't of `Selenium2Driver` class. +- The remote code coverage code made more reusable/testable through usage of OOP approach. +- Allow using both PHPUnit 3.x and PHPUnit 4.x versions. + +## [1.0.1] - 2013-11-12 +### Changed +- Use official Mockery repository with protected method mocking support. + +## [1.0.0] - 2013-07-13 +### Added +- Initial release. + +[Unreleased]: https://github.com/minkphp/phpunit-mink/compare/v2.5.0...HEAD +[2.5.0]: https://github.com/minkphp/phpunit-mink/compare/v2.4.0...v2.5.0 +[2.4.0]: https://github.com/minkphp/phpunit-mink/compare/v2.3.0...v2.4.0 +[2.3.0]: https://github.com/minkphp/phpunit-mink/compare/v2.2.0...v2.3.0 +[2.2.0]: https://github.com/minkphp/phpunit-mink/compare/v2.1.1...v2.2.0 +[2.1.1]: https://github.com/minkphp/phpunit-mink/compare/v2.1.0...v2.1.1 +[2.1.0]: https://github.com/minkphp/phpunit-mink/compare/v2.0.1...v2.1.0 +[2.0.1]: https://github.com/minkphp/phpunit-mink/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/minkphp/phpunit-mink/compare/v1.1.0...v2.0.0 +[1.1.0]: https://github.com/minkphp/phpunit-mink/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/minkphp/phpunit-mink/compare/v1.0.0...v1.0.1 + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f63b2db --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing +PHPUnit-Mink is an open source, community-driven project. If you'd like to contribute, feel free to do this, but remember to follow these few simple rules: + +## Submitting an issues +- A reproducible example is required for every bug report, otherwise it will most probably be __closed without warning__. +- If you are going to make a big, substantial change, let's discuss it first. + +## Working with Pull Requests +1. Create your feature addition or a bug fix branch based on `master` branch in your repository's fork. +2. Make necessary changes, but __don't mix__ code reformatting with code changes on topic. +3. Add tests for those changes (please look into `tests/` folder for some examples). This is important so we don't break it in a future version unintentionally. +4. Commit your code. +5. Squash your commits by topic to preserve a clean and readable log. +6. Create Pull Request. + +# Running tests +Make sure that you don't break anything with your changes by running: + +```bash +$> phpunit +``` + +## Checking coding standard violations + +This library uses [Coding Standard](https://github.com/aik099/CodingStandard) to ensure consistent formatting across the code base. Make sure you haven't introduced any Coding Standard violations by running following command in the root folder of the library: + +```bash +$> phpcs --standard="vendor/aik099/coding-standard/CodingStandard" library tests +``` + +or by making your IDE ([instructions for PhpStorm](http://www.jetbrains.com/phpstorm/webhelp/using-php-code-sniffer-tool.html)) to check them automatically. diff --git a/CodingStandard/Sniffs/Classes/ClassDeclarationSniff.php b/CodingStandard/Sniffs/Classes/ClassDeclarationSniff.php deleted file mode 100644 index 2e7bdf3..0000000 --- a/CodingStandard/Sniffs/Classes/ClassDeclarationSniff.php +++ /dev/null @@ -1,175 +0,0 @@ - - * @author Marc McIntyre - * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence - * @link http://pear.php.net/package/PHP_CodeSniffer - */ - -if (class_exists('PSR2_Sniffs_Classes_ClassDeclarationSniff', true) === false) { - $error = 'Class PSR2_Sniffs_Classes_ClassDeclarationSniff not found'; - throw new PHP_CodeSniffer_Exception($error); -} - -/** - * Class Declaration Test. - * - * Checks the declaration of the class and its inheritance is correct. - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood - * @author Marc McIntyre - * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence - * @version Release: 1.4.5 - * @link http://pear.php.net/package/PHP_CodeSniffer - */ -class CodingStandard_Sniffs_Classes_ClassDeclarationSniff extends PSR2_Sniffs_Classes_ClassDeclarationSniff -{ - - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) - { - // We want all the errors from the PSR2 standard, plus some of our own. - parent::process($phpcsFile, $stackPtr); - - $tokens = $phpcsFile->getTokens(); - - // Check that this is the only class or interface in the file. - $nextClass = $phpcsFile->findNext(array(T_CLASS, T_INTERFACE), ($stackPtr + 1)); - if ($nextClass !== false) { - // We have another, so an error is thrown. - $error = 'Only one interface or class is allowed in a file'; - $phpcsFile->addError($error, $nextClass, 'MultipleClasses'); - } - - }//end process() - - - /** - * Processes the opening section of a class declaration. - * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function processOpen(PHP_CodeSniffer_File $phpcsFile, $stackPtr) - { - parent::processOpen($phpcsFile, $stackPtr); - - $tokens = $phpcsFile->getTokens(); - - if ($tokens[($stackPtr - 1)]['code'] === T_WHITESPACE) { - $prevContent = $tokens[($stackPtr - 1)]['content']; - if ($prevContent !== $phpcsFile->eolChar) { - $blankSpace = substr($prevContent, strpos($prevContent, $phpcsFile->eolChar)); - $spaces = strlen($blankSpace); - - if (in_array($tokens[($stackPtr - 2)]['code'], array(T_ABSTRACT, T_FINAL)) === false) { - if ($spaces !== 0) { - $type = strtolower($tokens[$stackPtr]['content']); - $error = 'Expected 0 spaces before %s keyword; %s found'; - $data = array( - $type, - $spaces, - ); - $phpcsFile->addError($error, $stackPtr, 'SpaceBeforeKeyword', $data); - } - } - } - }//end if - - }//end processOpen() - - - /** - * Processes the closing section of a class declaration. - * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function processClose(PHP_CodeSniffer_File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - $closeBrace = $tokens[$stackPtr]['scope_closer']; - if ($tokens[($closeBrace - 1)]['code'] === T_WHITESPACE) { - $prevContent = $tokens[($closeBrace - 1)]['content']; - if ($prevContent !== $phpcsFile->eolChar) { - $blankSpace = substr($prevContent, strpos($prevContent, $phpcsFile->eolChar)); - $spaces = strlen($blankSpace); - if ($spaces !== 0) { - if ($tokens[($closeBrace - 1)]['line'] !== $tokens[$closeBrace]['line']) { - $error = 'Expected 0 spaces before closing brace; newline found'; - $phpcsFile->addError($error, $closeBrace, 'NewLineBeforeCloseBrace'); - } else { - $error = 'Expected 0 spaces before closing brace; %s found'; - $data = array($spaces); - $phpcsFile->addError($error, $closeBrace, 'SpaceBeforeCloseBrace', $data); - } - } - } - } - - // Check that the closing brace has one blank line after it. - $nextContent = $phpcsFile->findNext(array(T_WHITESPACE, T_COMMENT), ($closeBrace + 1), null, true); - if ($nextContent === false) { - // No content found, so we reached the end of the file. - // That means there was no closing tag either. - /*$error = 'Closing brace of a %s must be followed by a blank line and then a closing PHP tag'; - $data = array($tokens[$stackPtr]['content']); - $phpcsFile->addError($error, $closeBrace, 'EndFileAfterCloseBrace', $data);*/ - } else { - $nextLine = $tokens[$nextContent]['line']; - $braceLine = $tokens[$closeBrace]['line']; - if ($braceLine === $nextLine) { - $error = 'Closing brace of a %s must be followed by a single blank line'; - $data = array($tokens[$stackPtr]['content']); - $phpcsFile->addError($error, $closeBrace, 'NoNewlineAfterCloseBrace', $data); - } else if ($nextLine !== ($braceLine + 2)) { - $difference = ($nextLine - $braceLine - 1); - $error = 'Closing brace of a %s must be followed by a single blank line; found %s'; - $data = array( - $tokens[$stackPtr]['content'], - $difference, - ); - $phpcsFile->addError($error, $closeBrace, 'NewlinesAfterCloseBrace', $data); - } - }//end if - - // Check the closing brace is on it's own line, but allow - // for comments like "//end class". - $nextContent = $phpcsFile->findNext(T_COMMENT, ($closeBrace + 1), null, true); - if ($tokens[$nextContent]['content'] !== $phpcsFile->eolChar && $tokens[$nextContent]['line'] === $tokens[$closeBrace]['line']) { - $type = strtolower($tokens[$stackPtr]['content']); - $error = 'Closing %s brace must be on a line by itself'; - $data = array($tokens[$stackPtr]['content']); - $phpcsFile->addError($error, $closeBrace, 'CloseBraceSameLine', $data); - } - - }//end processClose() - - -}//end class diff --git a/CodingStandard/ruleset.xml b/CodingStandard/ruleset.xml deleted file mode 100644 index f0bf8a5..0000000 --- a/CodingStandard/ruleset.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - Alexander Obuhovich's coding standard. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/README.md b/README.md index 938e28d..74ea2bd 100644 --- a/README.md +++ b/README.md @@ -1,251 +1,40 @@ -# phpunit-mink -[![Build Status](https://travis-ci.org/aik099/phpunit-mink.png?branch=master)](https://travis-ci.org/aik099/phpunit-mink) -[![Coverage Status](https://coveralls.io/repos/aik099/phpunit-mink/badge.png?branch=master)](https://coveralls.io/r/aik099/phpunit-mink?branch=master) +# PHPUnit-Mink +[![CI](https://github.com/minkphp/phpunit-mink/actions/workflows/tests.yml/badge.svg)](https://github.com/minkphp/phpunit-mink/actions/workflows/tests.yml) +[![Docs](https://github.com/minkphp/phpunit-mink/actions/workflows/docs.yml/badge.svg)](https://github.com/minkphp/phpunit-mink/actions/workflows/docs.yml) +[![Documentation Status](https://readthedocs.org/projects/phpunit-mink/badge/?version=latest)](https://phpunit-mink.readthedocs.io/en/latest/?badge=latest) -[![Latest Stable Version](https://poser.pugx.org/aik099/phpunit-mink/v/stable.png)](https://packagist.org/packages/aik099/phpunit-mink) -[![Total Downloads](https://poser.pugx.org/aik099/phpunit-mink/downloads.png)](https://packagist.org/packages/aik099/phpunit-mink) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/minkphp/phpunit-mink/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/minkphp/phpunit-mink/?branch=master) +[![codecov](https://codecov.io/gh/minkphp/phpunit-mink/branch/master/graph/badge.svg?token=GV4K2at1WW)](https://codecov.io/gh/minkphp/phpunit-mink) +[![Dependency Status](https://www.versioneye.com/user/projects/52ad65e0ec1375ead3000049/badge.svg?style=flat)](https://www.versioneye.com/user/projects/52ad65e0ec1375ead3000049) -This library is an extension for [PHPUnit](sebastianbergmann/phpunit), that allows to write tests with help of [Mink](Behat/Mink). +[![Latest Stable Version](https://poser.pugx.org/aik099/phpunit-mink/v/stable.svg)](https://packagist.org/packages/aik099/phpunit-mink) [![Total Downloads](https://poser.pugx.org/aik099/phpunit-mink/downloads.svg)](https://packagist.org/packages/aik099/phpunit-mink) [![Latest Unstable Version](https://poser.pugx.org/aik099/phpunit-mink/v/unstable.svg)](https://packagist.org/packages/aik099/phpunit-mink) [![License](https://poser.pugx.org/aik099/phpunit-mink/license.svg)](https://packagist.org/packages/aik099/phpunit-mink) -## Overview -This library allows to perform following things: +This library is an extension for [PHPUnit](https://phpunit.de), that allows to write tests with help of [Mink](https://github.com/minkphp/Mink). -* use [Mink](Behat/Mink) for browser session control -* each test in a test case can use independent browser session -* all tests in a test case can share session between them -* Selenium server connection details are decoupled from tests using them -* perform individual browser configuration for each test in a test case -* support for "[Sauce Labs](https://saucelabs.com/)" -* remote code coverage collection +## Documentation -Each mentioned above features is described in more detail below. +* https://phpunit-mink.readthedocs.io/ -## Basic Usage -1. create subclass from `\aik099\PHPUnit\BrowserTestCase` class -2. define used browser configurations in static `$browsers` property of that class -3. use `$this->getSession()` method in your tests to access [Mink](Behat/Mink) session +## Service Integrations -## Using Mink -Call `$this->getSession()` from a test to get running `\Behat\Mink\Session` object, which is already configured from test configuration. +https://saucelabs.com/ -```php -getSession(); - - $session->visit('http://www.google.com'); - - $this->assertTrue($session->getPage()->hasContent('Google')); - } - -} -``` - - -## Per-test Browser Configuration -It is possible to set individual browser configuration for each test in a test case by creating a `\aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration` class instance in `setUp` method of the test case. - -```php -getBrowserAliases()); -// $browser = new SauceLabsBrowserConfiguration($this->getBrowserAliases()); -// $browser->setSauce(array('username' => 'sauce_username', 'api_key' => 'sauce_api_key')); - - $browser->setHost('selenium_host')->setPort('selenium_port')->setTimeout(30); - $browser->setBrowserName('browser name')->setDesiredCapabilities(array('version' => '6.5')); - $browser->setBaseUrl('http://www.test-host.com'); - - $this->setBrowser($browser); - - parent::setUp(); - } - -} -``` - -## Sharing Browser Configuration Between Tests -It is possible to define a single browser configuration to be used for each test in test case. This can be done by defining static `$browsers` class variable as an array, where each item represents a single browser configuration. In that case each of the tests in a test case would be executed using each of defined browser configurations. - -```php - 'localhost', - 'port' => 4444, - 'browserName' => 'firefox', - 'baseUrl' => 'http://www.google.com', - ), - array( - 'host' => 'localhost', - 'port' => 4444, - 'browserName' => 'chrome', - 'baseUrl' => 'http://www.google.com', - ), - ); - - public function testUsingBrowsersArray() - { - echo sprintf("I'm executed using '%s' browser", $this->getBrowser()->getBrowserName()); - } - -} -``` - -## Sharing Session Between Tests -As a benefit of shared browser configuration, that was described above is an ability to not only share browser configuration, that is used to create [Mink](Behat/Mink) session, but to actually share created sessions between all tests in a test case. This can be done by adding `sessionStrategy` option to browser configuration. - -```php - 'localhost', - 'port' => 4444, - 'browserName' => 'firefox', - 'baseUrl' => 'http://www.google.com', - 'sessionStrategy' => 'shared', - ), - ); - -} -``` - -## Using Browser Aliases -All previous examples demonstrate various ways how browser configuration can be defined, but they all have same downside - server connection details stay hard-coded in test case classes. This could become very problematic if: - -* same test cases needs to be executed on different servers (e.g. each developer runs them on his own machine) -* due change of server connection details each test case class needs to be changed - -To solve this problem a browser aliases were introduced. Basically a browser alias is predefined browser configuration, that is available in the test case by it's alias. How it can be used: - -1. create base test case class, by extending BrowserTestCase class in the project with `getBrowserAliases` method in it. That method will return an associative array of a browser configurations (array key acts as alias name) -2. in any place, where browser configuration is defined use `'alias' => 'alias_name_here'` instead of actual browser configuration -3. feel free to override any part of configuration defined in alias -4. nested aliases are also supported - -```php - array( - 'host' => 'localhost', - 'port' => 4444, - 'browserName' => 'firefox', - 'baseUrl' => 'http://www.google.com', - ), - ); - } - -} - - -class ConcreteTest extends BrowserAliasTest -{ - - public static $browsers = array( - array( - 'alias' => 'example_alias', - - ), - array( - 'alias' => 'example_alias', - 'browserName' => 'chrome', - ), - ); -} - -``` - -## Using "Sauce Labs" -When using "Sauce Labs" account to perform Selenium server-based testing you need to specify `'sauce' => array('username' => '...', 'api_key' => '...')` instead of `host` or `port` settings. In all other aspects all will work the same as if all tests are running locally. - -## Remote Code Coverage -Browser tests are executed on different machine, then one, where code coverage information is collected (and tests are executed). To solve that problem this library uses remote coverage collection. Following steps needs to be performed before using this feature: - -### On Remote Server -Remote server is web-server, where website used in tests is located. - -1. Install [Xdebug](http://xdebug.org/) PHP extension on web-server -2. Copy `library/aik099/PHPUnit/Common/phpunit_coverage.php` into web-server's DocumentRoot directory. -3. In web-server's `php.ini` configuration file (or `.htaccess` file), configure `library/aik099/PHPUnit/Common/prepend.php` and `library/aik099/PHPUnit/Common/append.php` as the `auto_prepend_file` and `auto_append_file` setting values, respectively. - -### On Test Machine -This is machine, where PHPUnit tests are being executed. - -1. In test case class that extends `BrowserTestCase` class, add `protected $coverageScriptUrl = 'http://host/phpunit_coverage.php';` to specify the URL for the `phpunit_coverage.php` script (`host` should be replaced with web server's url). - -### How This Works -1. each test sets a special cookie on website under test -2. when cookie is present, then `prepend.php` script collects coverage information and `append.php` stores it on disk -3. once test finishes it queries `phpunit_coverage.php` script on remote server, which in turn returns collected coverage information -4. remote coverage information is then joined with coverage information collected locally on test machine - -## Browser Configuration in Details -Each browser configuration consists of the following settings: - -* `host` - host, where Selenium Server is located (defaults to `localhost`) -* `port` - port, on which Selenium Server is listening for incoming connections (defaults to `4444`) -* `timeout` - connection timeout of the server in seconds (defaults to `60`) -* `browserName` - name of browser to use (e.g. `firefox`, `chrome`, etc., defaults to `firefox`) -* `desiredCapabilities` - parameters, that specify additional browser configuration (e.g. browser version, platform, etc.) -* `baseUrl` - base url of website, that is tested -* `sauce` - Sauce Labs connection configuration (e.g. `array('username' => 'username_here', 'api_key' => 'api_key_here')`) - -There are also corresponding `set` and `get` methods for each of mentioned above settings, that allow to individually change them before test has started (from `setUp` method). - -## Using Composer - -1. Define the dependencies in your ```composer.json```: +Define the dependencies in your ```composer.json```: ```json { "require": { - "aik099/phpunit-mink": "dev-master" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/aik099/phpunit-mink" - }, - ] + "aik099/phpunit-mink": "~2.0" + } } ``` -2. Install/update your vendors: +Install/update your vendors: ```bash $ curl http://getcomposer.org/installer | php $ php composer.phar install -``` \ No newline at end of file +``` diff --git a/composer.json b/composer.json index f1de000..a48a954 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "aik099/phpunit-mink", - "description": "Library for using Mink in PHPUnit tests. Supports test case-wide session sharing.", - "keywords": ["PHPUnit", "Selenium", "Mink", "Tests"], - "homepage": "http://github.com/aik099/phpunit-mink", + "description": "Library for using Mink in PHPUnit tests. Supports session sharing between tests in a test case.", + "keywords": ["PHPUnit", "Selenium", "SauceLabs", "Sauce", "BrowserStack", "Mink", "Tests"], + "homepage": "http://github.com/minkphp/phpunit-mink", "license": "BSD-3-Clause", "authors": [ @@ -13,19 +13,36 @@ ], "require": { - "php": ">=5.3.2", - "behat/mink": "1.5.*", - "behat/mink-selenium2-driver": "*", - "phpunit/phpunit": "3.7.*" + "php": ">=5.6", + "behat/mink": "^1.8@dev", + "behat/mink-selenium2-driver": "~1.2", + "phpunit/phpunit": ">=4.8.35 <5|>=5.4.3", + "console-helpers/phpunit-compat": "^1.0.3" }, "require-dev": { - "mockery/mockery": "dev-master" + "aik099/coding-standard": "dev-master", + "mockery/mockery": "~0.9|^1.5", + "yoast/phpunit-polyfills": "^1.0" }, "autoload": { - "psr-0": { - "aik099\\": "./library/" + "psr-4": { + "aik099\\PHPUnit\\": "library/aik099/PHPUnit", + "PimpleCopy\\Pimple\\": "library/PimpleCopy/Pimple" + } + }, + + "autoload-dev": { + "psr-4": { + "tests\\aik099\\PHPUnit\\": "tests/aik099/PHPUnit", + "tests\\PimpleCopy\\Pimple\\": "tests/PimpleCopy/Pimple" + } + }, + + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..956eb5b --- /dev/null +++ b/composer.lock @@ -0,0 +1,2093 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b267284ec13645ff1a614f50a181cc1a", + "packages": [ + { + "name": "behat/mink", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/minkphp/Mink.git", + "reference": "e35f4695de8800fc776af34ebf665ad58ebdd996" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/e35f4695de8800fc776af34ebf665ad58ebdd996", + "reference": "e35f4695de8800fc776af34ebf665ad58ebdd996", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "symfony/css-selector": "^2.7|^3.0|^4.0|^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5 || ^9.5", + "symfony/debug": "^2.7|^3.0|^4.0|^5.0", + "symfony/phpunit-bridge": "^3.4.38 || ^4.4 || ^5.0.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)", + "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Browser controller/emulator abstraction for PHP", + "homepage": "https://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "support": { + "issues": "https://github.com/minkphp/Mink/issues", + "source": "https://github.com/minkphp/Mink/tree/v1.9.0" + }, + "time": "2021-10-11T11:58:47+00:00" + }, + { + "name": "behat/mink-selenium2-driver", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkSelenium2Driver.git", + "reference": "0dee8cceed7e198bf130b4af0fab0ffab6dab47f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/0dee8cceed7e198bf130b4af0fab0ffab6dab47f", + "reference": "0dee8cceed7e198bf130b4af0fab0ffab6dab47f", + "shasum": "" + }, + "require": { + "behat/mink": "~1.7@dev", + "instaclick/php-webdriver": "~1.1", + "php": ">=5.4" + }, + "require-dev": { + "mink/driver-testsuite": "dev-master" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pete Otaqui", + "email": "pete@otaqui.com", + "homepage": "https://github.com/pete-otaqui" + }, + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Selenium2 (WebDriver) driver for Mink framework", + "homepage": "https://mink.behat.org/", + "keywords": [ + "ajax", + "browser", + "javascript", + "selenium", + "testing", + "webdriver" + ], + "support": { + "issues": "https://github.com/minkphp/MinkSelenium2Driver/issues", + "source": "https://github.com/minkphp/MinkSelenium2Driver/tree/v1.5.0" + }, + "time": "2021-10-12T16:01:47+00:00" + }, + { + "name": "console-helpers/phpunit-compat", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/console-helpers/phpunit-compat.git", + "reference": "096c6e42ebd090415ab03d9ce9edff8ed978a319" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/console-helpers/phpunit-compat/zipball/096c6e42ebd090415ab03d9ce9edff8ed978a319", + "reference": "096c6e42ebd090415ab03d9ce9edff8ed978a319", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "aik099/coding-standard": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitcompat_autoloader.php" + ], + "psr-4": { + "Tests\\ConsoleHelpers\\PHPUnitCompat\\": "tests/PHPUnitCompat/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexander Obuhovich", + "email": "aik.bold@gmail.com" + } + ], + "description": "Compatibility layer for PHPUnit test cases/test suite to work on different major PHPUnit versions", + "support": { + "issues": "https://github.com/console-helpers/phpunit-compat/issues", + "source": "https://github.com/console-helpers/phpunit-compat/tree/v1.0.3" + }, + "time": "2024-07-11T11:28:01+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.0.5" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "instaclick/php-webdriver", + "version": "1.4.19", + "source": { + "type": "git", + "url": "https://github.com/instaclick/php-webdriver.git", + "reference": "3b2a2ddc4e0a690cc691d7e5952964cc4b9538b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/3b2a2ddc4e0a690cc691d7e5952964cc4b9538b1", + "reference": "3b2a2ddc4e0a690cc691d7e5952964cc4b9538b1", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.5", + "satooshi/php-coveralls": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "WebDriver": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Justin Bishop", + "email": "jubishop@gmail.com", + "role": "Developer" + }, + { + "name": "Anthon Pang", + "email": "apang@softwaredevelopment.ca", + "role": "Fork Maintainer" + } + ], + "description": "PHP WebDriver for Selenium 2", + "homepage": "http://instaclick.com/", + "keywords": [ + "browser", + "selenium", + "webdriver", + "webtest" + ], + "support": { + "issues": "https://github.com/instaclick/php-webdriver/issues", + "source": "https://github.com/instaclick/php-webdriver/tree/1.4.19" + }, + "time": "2024-03-19T01:58:53+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.x" + }, + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" + }, + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/3.x" + }, + "time": "2017-11-10T14:09:06+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/master" + }, + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" + }, + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + }, + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + }, + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" + }, + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" + }, + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", + "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" + }, + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:45:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + }, + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + }, + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/master" + }, + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/master" + }, + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + }, + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" + }, + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + }, + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" + }, + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33", + "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v3.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.19.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", + "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.19-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T09:01:57+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "88289caa3c166321883f67fe5130188ebbb47094" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", + "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v3.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" + } + ], + "packages-dev": [ + { + "name": "aik099/coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/aik099/CodingStandard.git", + "reference": "7f80da035830935c5bc1a44eb76302393ab70f0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aik099/CodingStandard/zipball/7f80da035830935c5bc1a44eb76302393ab70f0a", + "reference": "7f80da035830935c5bc1a44eb76302393ab70f0a", + "shasum": "" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexander Obuhovich", + "email": "aik.bold@gmail.com" + } + ], + "description": "The PHP_CodeSniffer coding standard I'm using on all of my projects.", + "keywords": [ + "PHP_CodeSniffer", + "codesniffer" + ], + "support": { + "issues": "https://github.com/aik099/CodingStandard/issues", + "source": "https://github.com/aik099/CodingStandard/tree/master" + }, + "time": "2021-11-07T16:55:51+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "files": [ + "hamcrest/Hamcrest.php" + ], + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/master" + }, + "time": "2015-05-11T14:41:42+00:00" + }, + { + "name": "mockery/mockery", + "version": "0.9.11", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/be9bf28d8e57d67883cba9fcadfcff8caab667f8", + "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/0.9" + }, + "time": "2019-02-12T16:07:13+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "a0f7d708794a738f328d7b6c94380fd1d6c40446" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/a0f7d708794a738f328d7b6c94380fd1d6c40446", + "reference": "a0f7d708794a738f328d7b6c94380fd1d6c40446", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2024-04-05T16:01:51+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "behat/mink": 20, + "aik099/coding-standard": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..732a350 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,180 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +livehtml: + sphinx-autobuild --host 0.0.0.0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PHPUnit-Mink.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PHPUnit-Mink.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/PHPUnit-Mink" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PHPUnit-Mink" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..72da0b4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +# PHPUnit-Mink Documentation + +## Preparations + +1. install [Sphinx](http://sphinx-doc.org/): `easy_install -U Sphinx` +2. install [Read the Docs Sphinx Theme](https://github.com/snide/sphinx_rtd_theme): `pip install sphinx_rtd_theme` +3. install [sphinx-autobuild](https://pypi.python.org/pypi/sphinx-autobuild/0.2.3): `pip install sphinx-autobuild` + +## Automatic building + +1. run `make livehtml` in the `docs` folder +2. open `http://localhost:8000/` to view the documentation + +Thanks to the __sphinx-autobuild__ the documentation will be automatically built on every change and all browsers, +where it's opened will be reloaded automatically as well. + +## One-time building + +1. run `make html` in the `docs` folder +2. open `docs/build/html` folder in your browser diff --git a/docs/assets/images/browserstack_logo.png b/docs/assets/images/browserstack_logo.png new file mode 100644 index 0000000..0ac1474 Binary files /dev/null and b/docs/assets/images/browserstack_logo.png differ diff --git a/docs/assets/images/saucelabs_logo.png b/docs/assets/images/saucelabs_logo.png new file mode 100644 index 0000000..0babcc0 Binary files /dev/null and b/docs/assets/images/saucelabs_logo.png differ diff --git a/docs/browser-aliases.rst b/docs/browser-aliases.rst new file mode 100644 index 0000000..5b4261e --- /dev/null +++ b/docs/browser-aliases.rst @@ -0,0 +1,22 @@ +Browser Aliases +=============== +All previous examples demonstrate various ways how browser configuration can be defined, but they all have +same downside - server connection details stay hard-coded in test case classes. This could become very +problematic when: + +* the same test cases needs to be executed on the different servers (e.g. each developer runs them on his own machine) +* due change in the server connection details each test case class needs to be changed + +To solve this problem a browser aliases were introduced. Basically a browser alias is a predefined browser +configuration, that is available in the test case by it's alias. Here is how it can be used: + +#. create base test case class, by extending ``BrowserTestCase`` class in the project + with ``getBrowserAliases`` method in it +#. the ``getBrowserAliases`` method will return an associative array of a browser configurations (array key acts as an alias name) +#. in any place, where browser configuration is defined use ``'alias' => 'alias_name_here'`` instead of actual browser configuration +#. feel free to override any part of the configuration defined in the alias + +.. note:: Nested aliases are also supported. + +.. literalinclude:: examples/browser_aliases.php + :linenos: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..f42f57a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# Use theme, that was once default, but now replaced by the Alabaster +import sphinx_rtd_theme +html_theme = 'sphinx_rtd_theme' +#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx_rtd_theme', + 'sphinx.ext.autodoc', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'PHPUnit-Mink' +copyright = u'2014, Alexander Obuhovich' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +highlight_language = 'php' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# -- Options for HTML output ---------------------------------------------- + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PHPUnit-Minkdoc' + +# -- Options for LaTeX output --------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'PHPUnit-Mink.tex', u'PHPUnit-Mink Documentation', + u'Alexander Obuhovich', 'manual'), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'phpunit-mink', u'PHPUnit-Mink Documentation', + [u'Alexander Obuhovich'], 1) +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PHPUnit-Mink', u'PHPUnit-Mink Documentation', + u'Alexander Obuhovich', 'PHPUnit-Mink', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 0000000..c563f1b --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,74 @@ +Configuring Browser +=================== +The browser needs to be configured in a test case before being able to access the `Mink`_ session. +All possible ways of the browser configuration are described below. + +Per Test Configuration +^^^^^^^^^^^^^^^^^^^^^^ +It is possible to configure browser individually for each test within a test case by creating +an instance of the ``\aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration`` class in the +``setUpTest`` method of test case class and setting it through the ``setBrowser`` method. + +.. literalinclude:: examples/configuration/config_via_setup_method.php + :linenos: + :emphasize-lines: 14,19,28,37-41,44 + +Per Test Case Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In case, when all tests in a test case share same browser configuration it's easier to specify it via +the static ``$browsers`` property (array, where each element represents a single browser configuration) +in that test case class. + +.. literalinclude:: examples/configuration/config_via_browsers_property.php + :linenos: + :emphasize-lines: 8,9,16 + +.. note:: When several browser configurations are specified in the ``$browsers`` array, then each test + in a test case will be executed against each of the browser configurations. + +Browser Session Sharing +^^^^^^^^^^^^^^^^^^^^^^^ +As a benefit of the shared (per test case) browser configuration, that was described above is an ability +to not only share the browser configuration, that is used to create `Mink`_ session, but to actually share +created sessions between all tests in a single test case. This can be done by adding the ``sessionStrategy`` +option (line 15) to the browser configuration. + +.. literalinclude:: examples/configuration/per_test_case_browser_config.php + :linenos: + :emphasize-lines: 15,26,48 + +Selecting the Mink Driver +^^^^^^^^^^^^^^^^^^^^^^^^^ +With the help of the ``driver`` and the ``driverOptions`` browser configuration settings (since v2.1.0) it's possible to +specify which `Mink`_ driver to use. This file demonstrates how to use each driver: + +.. literalinclude:: examples/configuration/driver_showcase.php + :linenos: + :emphasize-lines: 10,20,32,40 + +Configuration Options +^^^^^^^^^^^^^^^^^^^^^ +Each browser configuration consists of the following settings (all optional): + +======================= ================================================================================================== +Name Description +======================= ================================================================================================== +``driver`` Mink driver name (defaults to the ``selenium2``, since v2.1.0) +``driverOptions`` Mink driver specific options (since v2.1.0) +``host`` host, where driver's server is located (defaults to the ``localhost``) +``port`` port, on which driver's server is listening for the incoming connections (determined by the driver) +``timeout`` connection timeout of the server in seconds ('selenium2' driver only, defaults to ``60``) +``browserName`` name of the browser to use (e.g. ``firefox``, ``chrome``, etc., defaults to the ``firefox``) +``desiredCapabilities`` parameters, that allow to fine-tune browser and other ``selenium2`` driver options (e.g. ``tags``, + ``project``, ``os``, ``version``) +``baseUrl`` base url of the website, that is tested +``sessionStrategy`` used session strategy (defaults to ``isolated``) +``type`` type of the configuration (defaults to ``default``, but also can be ``saucelabs`` or ``browserstack``) +``apiUsername`` API username of the used service (applicable to the ``saucelabs`` and ``browserstack`` browser configurations) +``apiKey`` API key of the used service (applicable to ``saucelabs`` and ``browserstack`` browser configurations) +======================= ================================================================================================== + +There are also corresponding setters (e.g. ``setHost``) and getters (e.g. ``getHost``) for each of the mentioned +above settings, that allow to individually change them from the ``setUpTest`` method before test has started. + +.. _`Mink`: https://github.com/minkphp/Mink diff --git a/docs/examples/browser_aliases.php b/docs/examples/browser_aliases.php new file mode 100644 index 0000000..5c3f782 --- /dev/null +++ b/docs/examples/browser_aliases.php @@ -0,0 +1,36 @@ + array( + 'driver' => 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + ), + ); + } + +} + + +class ConcreteTest extends BrowserAliasTestCase +{ + + public static $browsers = array( + array( + 'alias' => 'example_alias', + ), + array( + 'alias' => 'example_alias', + 'browserName' => 'chrome', + ), + ); +} diff --git a/docs/examples/configuration/config_via_browsers_property.php b/docs/examples/configuration/config_via_browsers_property.php new file mode 100644 index 0000000..604342f --- /dev/null +++ b/docs/examples/configuration/config_via_browsers_property.php @@ -0,0 +1,25 @@ + 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + ), + array( + 'driver' => 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'chrome', + 'baseUrl' => 'http://www.google.com', + ), + ); + +} diff --git a/docs/examples/configuration/config_via_setup_method.php b/docs/examples/configuration/config_via_setup_method.php new file mode 100644 index 0000000..07ef629 --- /dev/null +++ b/docs/examples/configuration/config_via_setup_method.php @@ -0,0 +1,49 @@ +createBrowserConfiguration(array( + // options goes here (optional) + )); + + // Creates the "Sauce Labs" browser configuration using "BrowserConfigurationFactory". + $browser = $this->createBrowserConfiguration(array( + // required + 'type' => 'saucelabs', + 'apiUsername' => 'sauce_username', + 'apiKey' => 'sauce_api_key', + // optional options goes here + )); + + // Creates the "BrowserStack" browser configuration using "BrowserConfigurationFactory". + $browser = $this->createBrowserConfiguration(array( + // required + 'type' => 'browserstack', + 'apiUsername' => 'bs_username', + 'apiKey' => 'bs_api_key', + // optional options goes here + )); + + // Options can be changed later (optional). + $browser->setHost('selenium_host')->setPort('selenium_port')->setTimeout(30); + $browser->setBrowserName('browser name')->setDesiredCapabilities(array( + 'version' => '6.5' + )); + $browser->setBaseUrl('http://www.test-host.com'); + + // Set browser configuration to the test case. + $this->setBrowser($browser); + + parent::setUpTest(); + } + +} diff --git a/docs/examples/configuration/driver_showcase.php b/docs/examples/configuration/driver_showcase.php new file mode 100644 index 0000000..923fb1f --- /dev/null +++ b/docs/examples/configuration/driver_showcase.php @@ -0,0 +1,61 @@ + 'goutte', + + // Defaults for this driver. + 'driverOptions' => array( + 'server_parameters' => array(), + 'guzzle_parameters' => array(), + ), + + ), + array( + 'driver' => 'sahi', + + // Defaults for this driver. + 'port' => 9999, + 'driverOptions' => array( + 'sid' => null, + 'limit' => 600, + 'browser' => null, + ), + ), + + array( + 'driver' => 'selenium2', + + // Defaults for this driver. + 'port' => 4444, + 'driverOptions' => array(), + ), + + array( + 'driver' => 'webdriver-classic', + + // Defaults for this driver. + 'port' => 4444, + 'driverOptions' => array(), + ), + + array( + 'driver' => 'zombie', + + // Defaults for this driver. + 'port' => 8124, + 'driverOptions' => array( + 'node_bin' => 'node', + 'server_path' => null, + 'threshold' => 2000000, + 'node_modules_path' => '', + ), + ), + ); + +} diff --git a/docs/examples/configuration/per_test_case_browser_config.php b/docs/examples/configuration/per_test_case_browser_config.php new file mode 100644 index 0000000..689cab5 --- /dev/null +++ b/docs/examples/configuration/per_test_case_browser_config.php @@ -0,0 +1,57 @@ + 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + 'sessionStrategy' => 'shared', + ), + ); + + /** + * @before + */ + public function setUpTest() + { + parent::setUpTest(); + + if ( $this->getSessionStrategy()->isFreshSession() ) { + // login once before any of the tests was started + } + } + + public function testOne() + { + // user will be already logged-in regardless + // of the test execution order/filtering + } + + public function testTwo() + { + // user will be already logged-in regardless + // of the test execution order/filtering + } + + /** + * @inheritDoc + */ + public function onTestSuiteEnded() + { + $session = $this->getSession(false); + + if ( $session !== null && $session->isStarted() ) { + // logout once after all the tests were finished + } + + return parent::onTestSuiteEnded(); + } + +} diff --git a/docs/examples/getting-started/cloud_selenium_configs.php b/docs/examples/getting-started/cloud_selenium_configs.php new file mode 100644 index 0000000..df74b36 --- /dev/null +++ b/docs/examples/getting-started/cloud_selenium_configs.php @@ -0,0 +1,37 @@ + 'saucelabs', + 'apiUsername' => '...', + 'apiKey' => '...', + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + ), + + // BrowserStack browser configuration. + array( + 'type' => 'browserstack', + 'apiUsername' => '...', + 'apiKey' => '...', + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + ), + + // Regular browser configuration. + array( + 'driver' => 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'chrome', + 'baseUrl' => 'http://www.google.com', + ), + ); + +} diff --git a/docs/examples/getting-started/general_test.php b/docs/examples/getting-started/general_test.php new file mode 100644 index 0000000..5f6bef1 --- /dev/null +++ b/docs/examples/getting-started/general_test.php @@ -0,0 +1,39 @@ + 'selenium2', + 'host' => 'localhost', + 'port' => 4444, + 'browserName' => 'firefox', + 'baseUrl' => 'http://www.google.com', + ), + ); + + public function testUsingSession() + { + // This is Mink's Session. + $session = $this->getSession(); + + // Go to a page. + $session->visit('http://www.google.com'); + + // Validate text presence on a page. + $this->assertTrue($session->getPage()->hasContent('Google')); + } + + public function testUsingBrowser() + { + // Prints the name of used browser. + echo sprintf( + "I'm executed using '%s' browser", + $this->getBrowser()->getBrowserName() + ); + } + +} diff --git a/docs/getting-started.rst b/docs/getting-started.rst new file mode 100644 index 0000000..1a222e4 --- /dev/null +++ b/docs/getting-started.rst @@ -0,0 +1,69 @@ +Getting Started +=============== +Below you'll find all the needed information to find your way across the library. + +Installation +^^^^^^^^^^^^ +The library can be installed using Composer like so: + +1. define the dependencies in your ``composer.json``: + +.. code-block:: json + + { + "require": { + "aik099/phpunit-mink": "~2.0" + } + } + +2. install/update your vendors: + +.. code-block:: bash + + $ curl http://getcomposer.org/installer | php + $ php composer.phar install + +Basic Usage +^^^^^^^^^^^ +#. sub-class the test case class from the ``\aik099\PHPUnit\BrowserTestCase`` class (line 5) +#. define used browser configurations in the static ``$browsers`` property of that class (line 8-16) +#. access the `Mink`_ session by calling the ``$this->getSession()`` method in your test (line 21) +#. access browser configuration by calling the ``$this->getBrowser()`` method in your test (line 35) + +.. literalinclude:: examples/getting-started/general_test.php + :linenos: + :emphasize-lines: 5,8-16,21,35 + +Selenium in the Cloud +^^^^^^^^^^^^^^^^^^^^^ +When using Selenium-based solution for the automated testing in the cloud (e.g. `Sauce Labs`_ or `BrowserStack`_) +you'll need to specify the following settings: + +* ``'type' => 'saucelabs'`` or ``'type' => 'browserstack'`` +* ``'apiUsername' => '...'`` +* ``'apiKey' => '...'`` + +instead of the ``host`` and ``port`` settings. In all other aspects everything will work the same as if all +tests were running locally. + +.. literalinclude:: examples/getting-started/cloud_selenium_configs.php + :linenos: + :emphasize-lines: 11-13,20-22 + +Continuous Integration +^^^^^^^^^^^^^^^^^^^^^^ +When the website under test isn't publicly accessible, then: + +#. secure tunnel needs to be created from the website under test to the server, that runs the tests +#. the created tunnel identifier needs to specified in the ``PHPUNIT_MINK_TUNNEL_ID`` environment variable + +.. note:: Before v2.1.0 the environment variable was called ``TRAVIS_JOB_NUMBER``. + +How to Create a Tunnel +---------------------- +* SauceLabs: https://docs.saucelabs.com/secure-connections/sauce-connect-5/ +* BrowserStack: https://www.browserstack.com/docs/automate/selenium/getting-started/php/local-testing + +.. _`Mink`: https://github.com/minkphp/Mink +.. _`Sauce Labs`: https://saucelabs.com/ +.. _`BrowserStack`: http://www.browserstack.com/ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..f864978 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,38 @@ +PHPUnit-Mink Documentation +========================== +This library is an extension for `PHPUnit `_, that allows to write tests +with help of `Mink`_. + +Overview +-------- +This library allows to perform following things: + +* use `Mink`_ for browser session control +* each test in a test case can use independent browser session +* all tests in a test case can share browser session between them +* Selenium server connection details are decoupled from tests using them +* perform individual browser configuration for each test in a test case +* support for `Sauce Labs `_ +* remote code coverage collection + +Each mentioned above features is described in more detail below. + +Service Integrations +-------------------- +|SauceLabs|_ |BrowserStack|_ + +.. |SauceLabs| image:: /assets/images/saucelabs_logo.png +.. _SauceLabs: https://saucelabs.com/ + +.. |BrowserStack| image:: /assets/images/browserstack_logo.png +.. _BrowserStack: https://www.browserstack.com/ + +.. _`Mink`: https://github.com/minkphp/Mink + +.. toctree:: + :maxdepth: 2 + + getting-started + configuration + browser-aliases + remote-code-coverage diff --git a/docs/remote-code-coverage.rst b/docs/remote-code-coverage.rst new file mode 100644 index 0000000..eabfab4 --- /dev/null +++ b/docs/remote-code-coverage.rst @@ -0,0 +1,43 @@ +Remote Code Coverage +==================== +Browser tests are executed on the different machine, than one, where the code coverage information is collected +(and tests are executed). To solve that problem this library uses remote coverage collection. Following +steps needs to be performed before using this feature: + +On Remote Server +^^^^^^^^^^^^^^^^ +This is web-server, where website used in tests is located. + +#. Install the `Xdebug`_ PHP extension on the web-server +#. Copy the ``library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageTool.php`` file into the web-server's DocumentRoot directory. +#. Include following code before your application bootstraps: + +.. code-block:: php + + setRemoteCoverageScriptUrl('http://host/'); + +How This Works +^^^^^^^^^^^^^^ +#. each test sets a special cookie on the website under test +#. when cookie is present, then the ``RemoteCoverageTool.php`` script collects coverage information and stores it on disk +#. once test finishes, then the ``http://host/?rct_mode=output`` url is accessed on remote server, which in turn returns collected coverage information +#. remote coverage information is then joined with coverage information collected locally on test machine + +.. _`Xdebug`: https://xdebug.org/ diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..4fedba9 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme==2.0.0 diff --git a/library/.phpstorm.meta.php b/library/.phpstorm.meta.php new file mode 100644 index 0000000..9ac0f13 --- /dev/null +++ b/library/.phpstorm.meta.php @@ -0,0 +1,15 @@ + \aik099\PHPUnit\Session\SessionStrategyFactory::class, + 'session_strategy_manager' => \aik099\PHPUnit\Session\SessionStrategyManager::class, + 'remote_url' => \aik099\PHPUnit\RemoteCoverage\RemoteUrl::class, + 'remote_coverage_helper' => \aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper::class, + 'test_suite_factory' => \aik099\PHPUnit\TestSuite\TestSuiteFactory::class, + 'regular_test_suite' => \aik099\PHPUnit\TestSuite\RegularTestSuite::class, + 'browser_test_suite' => \aik099\PHPUnit\TestSuite\BrowserTestSuite::class, + 'driver_factory_registry' => \aik099\PHPUnit\MinkDriver\DriverFactoryRegistry::class, + 'browser_configuration_factory' => \aik099\PHPUnit\BrowserConfiguration\BrowserConfigurationFactory::class, + ])); +} diff --git a/library/PimpleCopy/Pimple/Container.php b/library/PimpleCopy/Pimple/Container.php new file mode 100644 index 0000000..8970729 --- /dev/null +++ b/library/PimpleCopy/Pimple/Container.php @@ -0,0 +1,287 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ( $values as $key => $value ) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object. + * @param mixed $value The value of the parameter or a closure to define an object. + * + * @return void + * @throws \RuntimeException Prevent override of a frozen service. + */ + public function offsetSet($id, $value) + { + if ( isset($this->frozen[$id]) ) { + throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id)); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object. + * + * @return mixed The value of the parameter or an object + * @throws \InvalidArgumentException If the identifier is not defined. + */ + public function offsetGet($id) + { + if ( !isset($this->keys[$id]) ) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( isset($this->raw[$id]) + || !is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if ( isset($this->factories[$this->values[$id]]) ) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object. + * + * @return boolean + */ + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object. + * + * @return void + */ + public function offsetUnset($id) + { + if ( isset($this->keys[$id]) ) { + if ( is_object($this->values[$id]) ) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory. + * + * @return callable The passed callable + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object. + */ + public function factory($callable) + { + if ( !is_object($callable) || !method_exists($callable, '__invoke') ) { + throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated. + * + * @return callable The passed callable + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object. + */ + public function protect($callable) + { + if ( !is_object($callable) || !method_exists($callable, '__invoke') ) { + throw new \InvalidArgumentException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object. + * + * @return mixed The value of the parameter or the closure defining an object + * @throws \InvalidArgumentException If the identifier is not defined. + */ + public function raw($id) + { + if ( !isset($this->keys[$id]) ) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( isset($this->raw[$id]) ) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object. + * @param callable $callable A service definition to extend the original. + * + * @return callable The wrapped callable + * @throws \InvalidArgumentException If the identifier is not defined or not a service definition. + */ + public function extend($id, $callable) + { + if ( !isset($this->keys[$id]) ) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( !is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke') ) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id)); + } + + if ( !is_object($callable) || !method_exists($callable, '__invoke') ) { + throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if ( isset($this->factories[$factory]) ) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance. + * @param array $values An array of values that customizes the provider. + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $provider->register($this); + + foreach ( $values as $key => $value ) { + $this[$key] = $value; + } + + return $this; + } + +} diff --git a/library/PimpleCopy/Pimple/ServiceProviderInterface.php b/library/PimpleCopy/Pimple/ServiceProviderInterface.php new file mode 100644 index 0000000..e1d1d71 --- /dev/null +++ b/library/PimpleCopy/Pimple/ServiceProviderInterface.php @@ -0,0 +1,51 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\APIClient; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use WebDriver\SauceLabs\SauceRest; +use WebDriver\ServiceFactory; + +class APIClientFactory +{ + + /** + * Creates an API client based on a browser configuration. + * + * @param BrowserConfiguration $browser The browser configuration. + * + * @return IAPIClient + * @throws \LogicException When unsupported browser configuration was given. + */ + public function getAPIClient(BrowserConfiguration $browser) + { + if ( $browser instanceof BrowserStackBrowserConfiguration ) { + return new BrowserStackAPIClient( + $browser->getApiUsername(), + $browser->getApiKey(), + ServiceFactory::getInstance()->getService('service.curl') + ); + } + + if ( $browser instanceof SauceLabsBrowserConfiguration ) { + $sauce_rest = new SauceRest($browser->getApiUsername(), $browser->getApiKey()); + + return new SauceLabsAPIClient($sauce_rest); + } + + throw new \LogicException('The "' . $browser->getType() . '" browser configuration is not supported.'); + } + +} diff --git a/library/aik099/PHPUnit/APIClient/BrowserStackAPIClient.php b/library/aik099/PHPUnit/APIClient/BrowserStackAPIClient.php new file mode 100644 index 0000000..9798e7b --- /dev/null +++ b/library/aik099/PHPUnit/APIClient/BrowserStackAPIClient.php @@ -0,0 +1,114 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\APIClient; + + +use WebDriver\Service\CurlServiceInterface; + +class BrowserStackAPIClient implements IAPIClient +{ + + /** + * API username. + * + * @var string + */ + private $_apiUsername; + + /** + * API key. + * + * @var string + */ + private $_apiKey; + + /** + * Curl service. + * + * @var CurlServiceInterface + */ + private $_curlService; + + /** + * Creates instance of API client. + * + * @param string $api_username API Username. + * @param string $api_key API Password. + * @param CurlServiceInterface $curl_service Curl service. + */ + public function __construct($api_username, $api_key, CurlServiceInterface $curl_service) + { + $this->_apiUsername = $api_username; + $this->_apiKey = $api_key; + $this->_curlService = $curl_service; + } + + /** + * Returns information about session. + * + * @param string $session_id Session ID. + * + * @return array + */ + public function getInfo($session_id) + { + $result = $this->execute('GET', 'sessions/' . $session_id . '.json'); + + return $result['automation_session']; + } + + /** + * Update status of the test, that was executed in the given session. + * + * @param string $session_id Session ID. + * @param boolean $test_status Test status. + * @param string $test_status_message Test status message. + * + * @return array + */ + public function updateStatus($session_id, $test_status, $test_status_message) + { + $data = array('status' => $test_status ? 'passed' : 'failed'); + + if ( $test_status_message ) { + $data['reason'] = $test_status_message; + } + + $result = $this->execute('PUT', 'sessions/' . $session_id . '.json', $data); + + return $result['automation_session']; + } + + /** + * Execute BrowserStack REST API command. + * + * @param string $request_method HTTP request method. + * @param string $url URL. + * @param mixed $parameters Parameters. + * + * @return mixed + * @see http://www.browserstack.com/automate/rest-api + */ + protected function execute($request_method, $url, $parameters = null) + { + $extra_options = array( + CURLOPT_HTTPAUTH => CURLAUTH_BASIC, + CURLOPT_USERPWD => $this->_apiUsername . ':' . $this->_apiKey, + ); + + $url = 'https://www.browserstack.com/automate/' . $url; + + list($raw_results,) = $this->_curlService->execute($request_method, $url, $parameters, $extra_options); + + return json_decode($raw_results, true); + } + +} diff --git a/library/aik099/PHPUnit/APIClient/IAPIClient.php b/library/aik099/PHPUnit/APIClient/IAPIClient.php new file mode 100644 index 0000000..19bc025 --- /dev/null +++ b/library/aik099/PHPUnit/APIClient/IAPIClient.php @@ -0,0 +1,40 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\APIClient; + + +/** + * Interface that each API client must implement. + */ +interface IAPIClient +{ + + /** + * Returns information about session. + * + * @param string $session_id Session ID. + * + * @return array + */ + public function getInfo($session_id); + + /** + * Update status of the test, that was executed in the given session. + * + * @param string $session_id Session ID. + * @param boolean $test_status Test status. + * @param string $test_status_message Test status message. + * + * @return array + */ + public function updateStatus($session_id, $test_status, $test_status_message); + +} diff --git a/library/aik099/PHPUnit/APIClient/SauceLabsAPIClient.php b/library/aik099/PHPUnit/APIClient/SauceLabsAPIClient.php new file mode 100644 index 0000000..448fd32 --- /dev/null +++ b/library/aik099/PHPUnit/APIClient/SauceLabsAPIClient.php @@ -0,0 +1,71 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\APIClient; + + +use WebDriver\SauceLabs\SauceRest; + +/** + * @link https://docs.saucelabs.com/dev/api/jobs/ + */ +class SauceLabsAPIClient implements IAPIClient +{ + + /** + * API client for SauceLabs service. + * + * @var SauceRest + */ + private $_sauceRest; + + /** + * Creates instance of API client. + * + * @param SauceRest $sauce_rest SauceRest client. + */ + public function __construct(SauceRest $sauce_rest) + { + $this->_sauceRest = $sauce_rest; + } + + /** + * Returns information about session. + * + * @param string $session_id Session ID. + * + * @return array + */ + public function getInfo($session_id) + { + return $this->_sauceRest->getJob($session_id); + } + + /** + * Update status of the test, that was executed in the given session. + * + * @param string $session_id Session ID. + * @param boolean $test_status Test status. + * @param string $test_status_message Test status message. + * + * @return array + */ + public function updateStatus($session_id, $test_status, $test_status_message) + { + $data = array('passed' => $test_status); + + if ( $test_status_message ) { + $data['custom-data'] = array('status_message' => $test_status_message); + } + + return $this->_sauceRest->updateJob($session_id, $data); + } + +} diff --git a/library/aik099/PHPUnit/Application.php b/library/aik099/PHPUnit/Application.php new file mode 100644 index 0000000..fc66ab1 --- /dev/null +++ b/library/aik099/PHPUnit/Application.php @@ -0,0 +1,122 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit; + + +use aik099\PHPUnit\TestSuite\TestSuiteFactory; + +/** + * Main application class. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class Application +{ + + /** + * Dependency injection container. + * + * @var DIContainer + */ + protected $container; + + /** + * Returns instance of strategy manager. + * + * @param DIContainer $container Dependency injection container. + * + * @return self + */ + public static function getInstance(DIContainer $container = null) + { + static $instance = null; + + if ( null === $instance ) { + $instance = new static($container); + } + + return $instance; + } + + /** + * Prevents direct instantiation. + * + * @param DIContainer $container Dependency injection container. + */ + public function __construct(DIContainer $container = null) + { + if ( !isset($container) ) { + $container = new DIContainer(); + } + + $this->container = $container; + $this->container->setApplication($this); + } + + /** + * Returns test suite builder. + * + * @return TestSuiteFactory + * @see BrowserTestCase::suite() + */ + public function getTestSuiteFactory() + { + return $this->getObject('test_suite_factory'); + } + + /** + * Returns object from the container. + * + * @param string $service_id Name of the object in the container. + * + * @return \stdClass + */ + public function getObject($service_id) + { + return $this->container[$service_id]; + } + + /** + * Replaces object in the container. + * + * @param string $service_id Name of the object in the container. + * @param callable $callable The callable that will return the object. + * @param boolean $is_factory The callable should be considered as a factory. + * + * @return callable Previous service version. + * @throws \InvalidArgumentException When attempt is made to replace non-existing service. + */ + public function replaceObject($service_id, $callable, $is_factory = false) + { + if ( !isset($this->container[$service_id]) ) { + throw new \InvalidArgumentException('Service "' . $service_id . '" not found'); + } + + $backup = $this->container->raw($service_id); + unset($this->container[$service_id]); + $this->container[$service_id] = $is_factory ? $this->container->factory($callable) : $callable; + + return $backup; + } + + /** + * Prevents cloning. + * + * @return void + * + * @codeCoverageIgnore + */ + private function __clone() + { + + } + +} diff --git a/library/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfiguration.php b/library/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfiguration.php new file mode 100644 index 0000000..3799d6c --- /dev/null +++ b/library/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfiguration.php @@ -0,0 +1,231 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\APIClient\IAPIClient; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use Behat\Mink\Driver\Selenium2Driver; +use Behat\Mink\Session; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; + +/** + * Browser configuration tailored to use with API-based service. + * + * @method string getApiUsername() Returns API username. + * @method string getApiKey() Returns API key. + */ +abstract class ApiBrowserConfiguration extends BrowserConfiguration +{ + + /** + * The build number. + */ + const BUILD_NUMBER_CAPABILITY = 'build'; + + /** + * The test name. + */ + const NAME_CAPABILITY = 'name'; + + /** + * API client factory. + * + * @var APIClientFactory + */ + protected $apiClientFactory; + + /** + * Creates browser configuration. + * + * @param DriverFactoryRegistry $driver_factory_registry Driver factory registry. + * @param APIClientFactory $api_client_factory API client factory. + */ + public function __construct(DriverFactoryRegistry $driver_factory_registry, APIClientFactory $api_client_factory) + { + $this->defaults['driver'] = 'selenium2'; + $this->defaults['apiUsername'] = ''; + $this->defaults['apiKey'] = ''; + + $this->apiClientFactory = $api_client_factory; + + parent::__construct($driver_factory_registry); + } + + /** + * Sets API username. + * + * To be called from TestCase::setUp(). + * + * @param string $api_username API username. + * + * @return self + */ + public function setApiUsername($api_username) + { + return $this->setParameter('apiUsername', $api_username); + } + + /** + * Sets API key. + * + * To be called from TestCase::setUp(). + * + * @param string $api_key API key. + * + * @return self + */ + public function setApiKey($api_key) + { + return $this->setParameter('apiKey', $api_key); + } + + /** + * Sets API username. + * + * Used internally to to allow using "api_username" parameter and avoid BC break. + * + * @param string $api_username API username. + * + * @return self + * @deprecated + */ + protected function setApi_username($api_username) + { + return $this->setApiUsername($api_username); + } + + /** + * Sets API key. + * + * Used internally to to allow using "api_key" parameter and avoid BC break. + * + * @param string $api_key API key. + * + * @return self + * @deprecated + */ + protected function setApi_key($api_key) + { + return $this->setApiKey($api_key); + } + + /** + * Returns port from browser configuration. + * + * @return integer + */ + public function getPort() + { + return 80; + } + + /** + * Returns browser name from browser configuration. + * + * @return string + */ + public function getBrowserName() + { + $browser_name = parent::getBrowserName(); + + return strlen($browser_name) ? $browser_name : 'chrome'; + } + + /** + * @inheritDoc + */ + public function onTestSetup(BrowserTestCase $test_case) + { + parent::onTestSetup($test_case); + + $desired_capabilities = $this->getDesiredCapabilities(); + $desired_capabilities[self::NAME_CAPABILITY] = $this->getJobName($test_case); + + if ( getenv('BUILD_NUMBER') ) { + $desired_capabilities[self::BUILD_NUMBER_CAPABILITY] = getenv('BUILD_NUMBER'); // Jenkins. + } + elseif ( getenv('TRAVIS_BUILD_NUMBER') ) { + $desired_capabilities[self::BUILD_NUMBER_CAPABILITY] = getenv('TRAVIS_BUILD_NUMBER'); + } + + $this->setDesiredCapabilities($desired_capabilities); + } + + /** + * Returns Job name for API service. + * + * @param BrowserTestCase $test_case Browser test case. + * + * @return string + */ + protected function getJobName(BrowserTestCase $test_case) + { + if ( $this->isShared() ) { + return get_class($test_case); + } + + return $test_case->toString(); + } + + /** + * @inheritDoc + */ + public function onTestEnded(BrowserTestCase $test_case, TestResult $test_result) + { + parent::onTestEnded($test_case, $test_result); + + $session = $test_case->getSession(false); + + if ( $session === null || !$session->isStarted() ) { + // Session wasn't used in particular test. + return; + } + + $this->getAPIClient()->updateStatus( + $this->getSessionId($session), + $this->getTestStatus($test_case, $test_result), + $this->getTestStatusMessage($test_case, $test_result) + ); + } + + /** + * Returns API class for service interaction. + * + * @return IAPIClient + */ + public function getAPIClient() + { + return $this->apiClientFactory->getAPIClient($this); + } + + /** + * Get Selenium2 current session id. + * + * @param Session $session Session. + * + * @return string + * @throws \RuntimeException When session was created using an unsupported driver. + */ + protected function getSessionId(Session $session) + { + $driver = $session->getDriver(); + + if ( $driver instanceof Selenium2Driver ) { + return $driver->getWebDriverSessionId(); + } + + throw new \RuntimeException('Unsupported session driver'); + } + +} diff --git a/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfiguration.php b/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfiguration.php index c1bc544..98c077f 100644 --- a/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfiguration.php +++ b/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfiguration.php @@ -12,24 +12,66 @@ use aik099\PHPUnit\BrowserTestCase; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; -use Behat\Mink\Driver\Selenium2Driver; -use Behat\Mink\Session; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use Behat\Mink\Driver\DriverInterface; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; /** * Browser configuration for browser. * - * @method \Mockery\Expectation shouldReceive + * @method \Mockery\Expectation shouldReceive(string $name) + * @method string getDriver() Returns Mink driver name. + * @method array getDriverOptions() Returns Mink driver options. + * @method string getHost() Returns hostname from browser configuration. + * @method integer getPort() Returns port from browser configuration. + * @method string getBrowserName() Returns browser name from browser configuration. + * @method string getBaseUrl() Returns default browser url from browser configuration. + * @method array getDesiredCapabilities() Returns desired capabilities from browser configuration. + * @method integer getTimeout() Returns server timeout. + * @method string getSessionStrategy() Returns session strategy name. */ class BrowserConfiguration { + const TYPE = 'default'; /** - * Browser configuration. + * User defaults. * * @var array */ - protected $parameters; + protected $defaults = array( + // Driver related. + 'host' => 'localhost', + 'driver' => 'selenium2', + 'driverOptions' => array(), + + // TODO: Move under 'driverOptions' of 'selenium2'/'webdriver-classic' driver (BC break). + 'desiredCapabilities' => array(), + 'timeout' => 60, + + // Browser related. + 'browserName' => 'firefox', // Have no effect on headless drivers. + 'baseUrl' => '', + + // Test related. + 'sessionStrategy' => ISessionStrategyFactory::TYPE_ISOLATED, + ); + + /** + * User defaults merged with driver defaults. + * + * @var array + */ + private $_mergedDefaults = array(); + + /** + * Manually set browser configuration parameters. + * + * @var array + */ + private $_parameters = array(); /** * Browser configuration aliases. @@ -38,29 +80,83 @@ class BrowserConfiguration */ protected $aliases; + /** + * Driver factory registry. + * + * @var DriverFactoryRegistry + */ + private $_driverFactoryRegistry; + + /** + * Driver factory. + * + * @var IMinkDriverFactory + */ + private $_driverFactory; + + /** + * Resolves browser alias into corresponding browser configuration. + * + * @param array $parameters Browser configuration. + * @param array $aliases Browser configuration aliases. + * + * @return array + * @throws \InvalidArgumentException When unable to resolve used browser alias. + */ + public static function resolveAliases(array $parameters, array $aliases) + { + if ( !isset($parameters['alias']) ) { + return $parameters; + } + + $browser_alias = $parameters['alias']; + unset($parameters['alias']); + + if ( isset($aliases[$browser_alias]) ) { + $candidate_params = self::arrayMergeRecursive($aliases[$browser_alias], $parameters); + + return self::resolveAliases($candidate_params, $aliases); + } + + throw new \InvalidArgumentException(sprintf('Unable to resolve "%s" browser alias', $browser_alias)); + } + /** * Creates browser configuration. * - * @param array $aliases Browser configuration aliases. + * @param DriverFactoryRegistry $driver_factory_registry Driver factory registry. */ - public function __construct(array $aliases = array()) + public function __construct(DriverFactoryRegistry $driver_factory_registry) { - $this->parameters = array( - // server related - 'host' => 'localhost', - 'port' => 4444, - 'timeout' => 60, - - // browser related - 'browserName' => 'firefox', - 'desiredCapabilities' => array(), - 'baseUrl' => '', - - // test related - 'sessionStrategy' => SessionStrategyManager::ISOLATED_STRATEGY, - ); + $this->_driverFactoryRegistry = $driver_factory_registry; + + if ( $this->defaults['driver'] ) { + $this->setDriver($this->defaults['driver']); + } + } + /** + * Returns type of browser configuration. + * + * @return string + */ + public function getType() + { + return static::TYPE; + } + + /** + * Sets aliases. + * + * @param array $aliases Browser configuration aliases. + * + * @return self + */ + public function setAliases(array $aliases = array()) + { $this->aliases = $aliases; + + return $this; } /** @@ -69,79 +165,111 @@ public function __construct(array $aliases = array()) * @param array $parameters Browser configuration parameters. * * @return self + * @throws \InvalidArgumentException When unknown parameter is discovered. */ public function setup(array $parameters) { - $parameters = array_merge($this->parameters, self::resolveAliases($parameters, $this->aliases)); + $parameters = $this->prepareParameters($parameters); - $this->setHost($parameters['host'])->setPort($parameters['port'])->setTimeout($parameters['timeout']); - $this->setBrowserName($parameters['browserName'])->setDesiredCapabilities($parameters['desiredCapabilities']); - $this->setBaseUrl($parameters['baseUrl']); - $this->setSessionStrategy($parameters['sessionStrategy']); + // Make sure, that 'driver' parameter is handled first. + if ( isset($parameters['driver']) ) { + $this->setDriver($parameters['driver']); + unset($parameters['driver']); + } + + foreach ( $parameters as $name => $value ) { + $method = 'set' . ucfirst($name); + + if ( !method_exists($this, $method) ) { + throw new \InvalidArgumentException('Unable to set unknown parameter "' . $name . '"'); + } + + $this->$method($value); + } return $this; } /** - * Sets hostname to browser configuration. + * Merges together default, given parameter and resolves aliases along the way. * - * To be called from TestCase::setUp(). + * @param array $parameters Browser configuration parameters. * - * @param string $host Hostname. + * @return array + */ + protected function prepareParameters(array $parameters) + { + return array_merge($this->_parameters, self::resolveAliases($parameters, $this->aliases)); + } + + /** + * Sets Mink driver to browser configuration. + * + * @param string $driver_name Mink driver name. * * @return self - * @throws \InvalidArgumentException When host is not a string. + * @throws \InvalidArgumentException When Mink driver name is not a string. */ - public function setHost($host) + public function setDriver($driver_name) { - if ( !is_string($host) ) { - throw new \InvalidArgumentException('Host must be a string'); + if ( !is_string($driver_name) ) { + throw new \InvalidArgumentException('The Mink driver name must be a string'); } - $this->parameters['host'] = $host; + $this->_driverFactory = $this->_driverFactoryRegistry->get($driver_name); + $this->_mergedDefaults = self::arrayMergeRecursive($this->defaults, $this->_driverFactory->getDriverDefaults()); - return $this; + return $this->setParameter('driver', $driver_name); } /** - * Returns hostname from browser configuration. + * Sets Mink driver options to browser configuration. * - * @return string + * @param array $driver_options Mink driver options. + * + * @return self */ - public function getHost() + public function setDriverOptions(array $driver_options) { - return $this->parameters['host']; + return $this->setParameter('driverOptions', $driver_options); } /** - * Sets port to browser configuration. + * Sets hostname to browser configuration. * * To be called from TestCase::setUp(). * - * @param integer $port Port. + * @param string $host Hostname. * * @return self - * @throws \InvalidArgumentException When port isn't a number. + * @throws \InvalidArgumentException When host is not a string. */ - public function setPort($port) + public function setHost($host) { - if ( !is_int($port) ) { - throw new \InvalidArgumentException('Port must be an integer'); + if ( !is_string($host) ) { + throw new \InvalidArgumentException('Host must be a string'); } - $this->parameters['port'] = $port; - - return $this; + return $this->setParameter('host', $host); } /** - * Returns port from browser configuration. + * Sets port to browser configuration. * - * @return integer + * To be called from TestCase::setUp(). + * + * @param integer $port Port. + * + * @return self + * @throws \InvalidArgumentException When port isn't a number. */ - public function getPort() + public function setPort($port) { - return $this->parameters['port']; + if ( !is_int($port) ) { + throw new \InvalidArgumentException('Port must be an integer'); + } + + return $this->setParameter('port', $port); } /** @@ -160,19 +288,7 @@ public function setBrowserName($browser_name) throw new \InvalidArgumentException('Browser must be a string'); } - $this->parameters['browserName'] = $browser_name; - - return $this; - } - - /** - * Returns browser name from browser configuration. - * - * @return string - */ - public function getBrowserName() - { - return $this->parameters['browserName']; + return $this->setParameter('browserName', $browser_name); } /** @@ -191,19 +307,7 @@ public function setBaseUrl($base_url) throw new \InvalidArgumentException('Base url must be a string'); } - $this->parameters['baseUrl'] = $base_url; - - return $this; - } - - /** - * Returns default browser url from browser configuration. - * - * @return string - */ - public function getBaseUrl() - { - return $this->parameters['baseUrl']; + return $this->setParameter('baseUrl', $base_url); } /** @@ -214,28 +318,15 @@ public function getBaseUrl() * @param array $capabilities Desired capabilities. * * @return self - * @link http://code.google.com/p/selenium/wiki/JsonWireProtocol + * @link http://code.google.com/p/selenium/wiki/JsonWireProtocol */ public function setDesiredCapabilities(array $capabilities) { - $this->parameters['desiredCapabilities'] = $capabilities; - - return $this; - } - - /** - * Returns desired capabilities from browser configuration. - * - * @return array - */ - public function getDesiredCapabilities() - { - return $this->parameters['desiredCapabilities']; + return $this->setParameter('desiredCapabilities', $capabilities); } /** * Sets server timeout. - * * To be called from TestCase::setUp(). * * @param integer $timeout Server timeout in seconds. @@ -249,60 +340,78 @@ public function setTimeout($timeout) throw new \InvalidArgumentException('Timeout must be an integer'); } - $this->parameters['timeout'] = $timeout; + return $this->setParameter('timeout', $timeout); + } - return $this; + /** + * Sets session strategy name. + * + * @param string $session_strategy Session strategy name. + * + * @return self + */ + public function setSessionStrategy($session_strategy) + { + return $this->setParameter('sessionStrategy', $session_strategy); } /** - * Returns server timeout. + * Tells if browser configuration requires a session, that is shared across tests in a test case. * - * @return integer + * @return boolean */ - public function getTimeout() + public function isShared() { - return $this->parameters['timeout']; + return $this->getSessionStrategy() == ISessionStrategyFactory::TYPE_SHARED; } /** - * Sets session strategy name. + * Sets parameter. * - * @param string $session_strategy Session strategy name. + * @param string $name Parameter name. + * @param mixed $value Parameter value. * * @return self - * @throws \InvalidArgumentException When unknown session strategy name given. + * @throws \LogicException When driver wasn't set upfront. */ - public function setSessionStrategy($session_strategy) + protected function setParameter($name, $value) { - $allowed = array(SessionStrategyManager::ISOLATED_STRATEGY, SessionStrategyManager::SHARED_STRATEGY); - - if ( !in_array($session_strategy, $allowed) ) { - throw new \InvalidArgumentException(vsprintf('Session strategy must be either "%s" or "%s"', $allowed)); + if ( !isset($this->_driverFactory) ) { + throw new \LogicException('Please set "driver" parameter first.'); } - $this->parameters['sessionStrategy'] = $session_strategy; + $this->_parameters[$name] = $value; return $this; } /** - * Returns session strategy name. + * Returns parameter value. * - * @return string + * @param string $name Name. + * + * @return mixed + * @throws \InvalidArgumentException When unknown parameter was requested. */ - public function getSessionStrategy() + protected function getParameter($name) { - return $this->parameters['sessionStrategy']; + $merged = self::arrayMergeRecursive($this->_mergedDefaults, $this->_parameters); + + if ( array_key_exists($name, $merged) ) { + return $merged[$name]; + } + + throw new \InvalidArgumentException('Unable to get unknown parameter "' . $name . '"'); } /** - * Tells if browser configuration requires a session, that is shared across tests in a test case. + * Creates driver based on browser configuration. * - * @return boolean + * @return DriverInterface */ - public function isShared() + public function createDriver() { - return $this->getSessionStrategy() == SessionStrategyManager::SHARED_STRATEGY; + return $this->_driverFactory->createDriver($this); } /** @@ -310,11 +419,11 @@ public function isShared() * * @param BrowserTestCase $test_case Test case. * - * @return integer + * @return string */ public function getSessionStrategyHash(BrowserTestCase $test_case) { - $ret = $this->getBrowserHash(); + $ret = $this->getChecksum(); if ( $this->isShared() ) { $ret .= '::' . get_class($test_case); @@ -326,84 +435,67 @@ public function getSessionStrategyHash(BrowserTestCase $test_case) /** * Returns test run status based on session strategy requested by browser. * - * @param BrowserTestCase $test_case Browser test case. - * @param \PHPUnit_Framework_TestResult $test_result Test result. + * @param BrowserTestCase $test_case Browser test case. + * @param TestResult $test_result Test result. * * @return boolean * @see IsolatedSessionStrategy * @see SharedSessionStrategy */ - public function getTestStatus(BrowserTestCase $test_case, \PHPUnit_Framework_TestResult $test_result) + public function getTestStatus(BrowserTestCase $test_case, TestResult $test_result) { if ( $this->isShared() ) { - // all tests in a test case use same session -> failed even if 1 test fails + // All tests in a test case use same session -> failed even if 1 test fails. return $test_result->wasSuccessful(); } - // each test in a test case are using it's own session -> failed if test fails + // Each test in a test case are using it's own session -> failed if test fails. return !$test_case->hasFailed(); } /** - * Returns hash from current configuration. + * Returns test status message based on session strategy requested by browser. * - * @return integer - */ - protected function getBrowserHash() - { - ksort($this->parameters); - - return crc32(serialize($this->parameters)); - } - - /** - * Creates new session based on browser configuration. + * @param BrowserTestCase $test_case Browser test case. + * @param TestResult $test_result Test result. * - * @return Session + * @return boolean + * @see IsolatedSessionStrategy + * @see SharedSessionStrategy */ - public function createSession() + public function getTestStatusMessage(BrowserTestCase $test_case, TestResult $test_result) { - $capabilities = $this->getDesiredCapabilities(); - $capabilities['browserName'] = $this->getBrowserName(); + if ( $this->isShared() ) { + // All tests in a test case use same session -> failed even if 1 test fails. + $test_failures = array(); - // TODO: maybe doesn't work - ini_set('default_socket_timeout', $this->getTimeout()); + if ( $test_result->errorCount() > 0 ) { + $test_failures = $test_result->errors(); + } + elseif ( $test_result->failureCount() > 0 ) { + $test_failures = $test_result->failures(); + } + elseif ( $test_result->warningCount() > 0 ) { + $test_failures = $test_result->warnings(); + } - // create driver: - $driver = new Selenium2Driver( - $this->getBrowserName(), - $capabilities, - 'http://' . $this->getHost() . ':' . $this->getPort() . '/wd/hub' - ); + return $test_failures ? reset($test_failures)->exceptionMessage() : ''; + } - return new Session($driver); + // Each test in a test case are using it's own session -> failed if test fails. + return $test_case->getStatusMessage(); } /** - * Resolves browser alias into corresponding browser configuration. + * Returns checksum from current configuration. * - * @param array $parameters Browser configuration. - * @param array $aliases Browser configuration aliases. - * - * @return array - * @throws \InvalidArgumentException When unable to resolve used browser alias. + * @return integer */ - public static function resolveAliases(array $parameters, array $aliases) + public function getChecksum() { - if ( !isset($parameters['alias']) ) { - return $parameters; - } - - $browser_alias = $parameters['alias']; - unset($parameters['alias']); - - if ( isset($aliases[$browser_alias]) ) { - $candidate_params = self::arrayMergeRecursive($aliases[$browser_alias], $parameters); + ksort($this->_parameters); - return self::resolveAliases($candidate_params, $aliases); - } - - throw new \InvalidArgumentException(sprintf('Unable to resolve "%s" browser alias', $browser_alias)); + return crc32(serialize($this->_parameters)); } /** @@ -435,28 +527,50 @@ protected static function arrayMergeRecursive($array1, $array2) } /** - * Hook, called from "BrowserTestCase::setUp" method. + * Allows to retrieve a parameter by name. * - * @param BrowserTestCase $test_case Browser test case. + * @param string $method Method name. + * @param array $args Arguments. * - * @return self + * @return mixed + * @throws \BadMethodCallException When non-parameter getter method is invoked. */ - public function testSetUpHook(BrowserTestCase $test_case) + public function __call($method, array $args) { - return $this; + if ( substr($method, 0, 3) === 'get' ) { + return $this->getParameter(lcfirst(substr($method, 3))); + } + + throw new \BadMethodCallException( + 'Method "' . $method . '" does not exist on ' . get_class($this) . ' class' + ); } /** - * Hook, called from "BrowserTestCase::run" method. + * Hook, called from "BrowserTestCase::setUpTest" method. * - * @param BrowserTestCase $test_case Browser test case. - * @param \PHPUnit_Framework_TestResult $test_result Test result. + * @param BrowserTestCase $test_case Test case. * - * @return self + * @return void + * @internal */ - public function testAfterRunHook(BrowserTestCase $test_case, \PHPUnit_Framework_TestResult $test_result) + public function onTestSetup(BrowserTestCase $test_case) { - return $this; + // Place code here. + } + + /** + * Hook, called from "BrowserTestCase::tearDownTest" method. + * + * @param BrowserTestCase $test_case Test case. + * @param TestResult $test_result Test result. + * + * @return void + * @internal + */ + public function onTestEnded(BrowserTestCase $test_case, TestResult $test_result) + { + // Place code here. } } diff --git a/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactory.php b/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactory.php new file mode 100644 index 0000000..e1f23d2 --- /dev/null +++ b/library/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactory.php @@ -0,0 +1,88 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\BrowserTestCase; + +/** + * Browser configuration factory. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class BrowserConfigurationFactory implements IBrowserConfigurationFactory +{ + + /** + * Browser configurations. + * + * @var array + */ + protected $browserConfigurations = array(); + + /** + * Registers a browser configuration. + * + * @param BrowserConfiguration $browser Browser configuration. + * + * @return void + * @throws \InvalidArgumentException When browser configuration is already registered. + */ + public function register(BrowserConfiguration $browser) + { + $type = $browser->getType(); + + if ( isset($this->browserConfigurations[$type]) ) { + throw new \InvalidArgumentException( + 'Browser configuration with type "' . $type . '" is already registered' + ); + } + + $this->browserConfigurations[$type] = $browser; + } + + /** + * Returns browser configuration instance. + * + * @param array $config Browser. + * @param BrowserTestCase $test_case Test case. + * + * @return BrowserConfiguration + */ + public function createBrowserConfiguration(array $config, BrowserTestCase $test_case) + { + $aliases = $test_case->getBrowserAliases(); + $config = BrowserConfiguration::resolveAliases($config, $aliases); + + $type = isset($config['type']) ? $config['type'] : 'default'; + unset($config['type']); + + return $this->create($type)->setAliases($aliases)->setup($config); + } + + /** + * Creates browser configuration based on give type. + * + * @param string $type Type. + * + * @return BrowserConfiguration + * @throws \InvalidArgumentException When browser configuration not registered. + */ + protected function create($type) + { + if ( !isset($this->browserConfigurations[$type]) ) { + throw new \InvalidArgumentException('Browser configuration type "' . $type . '" not registered'); + } + + return clone $this->browserConfigurations[$type]; + } + +} diff --git a/library/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfiguration.php b/library/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfiguration.php new file mode 100644 index 0000000..2ddf8dd --- /dev/null +++ b/library/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfiguration.php @@ -0,0 +1,88 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\BrowserTestCase; + +/** + * Browser configuration tailored to use with "BrowserStack" service. + * + * @link https://www.browserstack.com/automate + */ +class BrowserStackBrowserConfiguration extends ApiBrowserConfiguration +{ + const TYPE = 'browserstack'; + + /** + * @inheritDoc + */ + public function onTestSetup(BrowserTestCase $test_case) + { + parent::onTestSetup($test_case); + + $desired_capabilities = $this->getDesiredCapabilities(); + + if ( getenv('PHPUNIT_MINK_TUNNEL_ID') ) { + $desired_capabilities['browserstack.local'] = 'true'; + $desired_capabilities['browserstack.localIdentifier'] = getenv('PHPUNIT_MINK_TUNNEL_ID'); + } + + $this->setDesiredCapabilities($desired_capabilities); + } + + /** + * Returns hostname from browser configuration. + * + * @return string + */ + public function getHost() + { + return $this->getApiUsername() . ':' . $this->getApiKey() . '@hub.browserstack.com'; + } + + /** + * Returns desired capabilities from browser configuration. + * + * @return array + * @link http://www.browserstack.com/automate/capabilities + * @link https://www.browserstack.com/docs/automate/selenium/select-browsers-and-devices#Selenium_Legacy_JSON + */ + public function getDesiredCapabilities() + { + $capabilities = parent::getDesiredCapabilities(); + + if ( !isset($capabilities['os']) ) { + $capabilities['os'] = 'Windows'; + $capabilities['os_version'] = '10'; + } + + if ( !isset($capabilities['acceptSslCerts']) ) { + $capabilities['acceptSslCerts'] = 'true'; + } + + return $capabilities; + } + + /** + * Returns Job name for API service. + * + * @param BrowserTestCase $test_case Browser test case. + * + * @return string + */ + protected function getJobName(BrowserTestCase $test_case) + { + // The BrowserStack started to replace "\" with " " some time ago. + return \str_replace('\\', '-', parent::getJobName($test_case)); + } + +} diff --git a/library/aik099/PHPUnit/BrowserConfiguration/IBrowserConfigurationFactory.php b/library/aik099/PHPUnit/BrowserConfiguration/IBrowserConfigurationFactory.php new file mode 100644 index 0000000..69e1570 --- /dev/null +++ b/library/aik099/PHPUnit/BrowserConfiguration/IBrowserConfigurationFactory.php @@ -0,0 +1,44 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\BrowserTestCase; + +/** + * Interface for browser factory creation. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +interface IBrowserConfigurationFactory +{ + + /** + * Returns browser configuration instance. + * + * @param array $config Browser. + * @param BrowserTestCase $test_case Test case. + * + * @return BrowserConfiguration + */ + public function createBrowserConfiguration(array $config, BrowserTestCase $test_case); + + /** + * Registers a browser configuration. + * + * @param BrowserConfiguration $browser Browser configuration. + * + * @return void + * @throws \InvalidArgumentException When browser configuration is already registered. + */ + public function register(BrowserConfiguration $browser); + +} diff --git a/library/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfiguration.php b/library/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfiguration.php index 217c87b..6d34f4f 100644 --- a/library/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfiguration.php +++ b/library/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfiguration.php @@ -12,75 +12,30 @@ use aik099\PHPUnit\BrowserTestCase; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; -use Behat\Mink\Driver\Selenium2Driver; -use WebDriver\SauceLabs\Capability as SauceLabsCapability; -use WebDriver\SauceLabs\SauceRest; /** * Browser configuration tailored to use with "Sauce Labs" service. + * + * @link https://saucelabs.com/php */ -class SauceLabsBrowserConfiguration extends BrowserConfiguration +class SauceLabsBrowserConfiguration extends ApiBrowserConfiguration { + const TYPE = 'saucelabs'; /** - * Creates browser configuration. - * - * @param array $aliases Browser configuration aliases. - */ - public function __construct(array $aliases = array()) - { - parent::__construct($aliases); - - $this->parameters['sauce'] = array('username' => '', 'api_key' => ''); - } - - /** - * Initializes a browser with given configuration. - * - * @param array $parameters Browser configuration parameters. - * - * @return self + * @inheritDoc */ - public function setup(array $parameters) + public function onTestSetup(BrowserTestCase $test_case) { - $parameters = array_merge($this->parameters, self::resolveAliases($parameters, $this->aliases)); - $this->setSauce($parameters['sauce']); + parent::onTestSetup($test_case); - return parent::setup($parameters); - } + $desired_capabilities = $this->getDesiredCapabilities(); - /** - * Sets "Sauce Labs" connection details. - * - * To be called from TestCase::setUp(). - * - * @param array $sauce Connection details. - * - * @return self - * @throws \InvalidArgumentException When incorrect sauce is given. - * @link https://saucelabs.com/php - */ - public function setSauce(array $sauce) - { - if ( !isset($sauce['username']) || !isset($sauce['api_key']) ) { - throw new \InvalidArgumentException('Incorrect sauce'); + if ( getenv('PHPUNIT_MINK_TUNNEL_ID') ) { + $desired_capabilities['tunnel-identifier'] = getenv('PHPUNIT_MINK_TUNNEL_ID'); } - $this->parameters['sauce'] = $sauce; - - return $this; - } - - /** - * Returns "Sauce Labs" connection details. - * - * @return array - * @link https://saucelabs.com/php - */ - public function getSauce() - { - return $this->parameters['sauce']; + $this->setDesiredCapabilities($desired_capabilities); } /** @@ -90,31 +45,7 @@ public function getSauce() */ public function getHost() { - $sauce = $this->getSauce(); - - return $sauce['username'] . ':' . $sauce['api_key'] . '@ondemand.saucelabs.com'; - } - - /** - * Returns port from browser configuration. - * - * @return integer - */ - public function getPort() - { - return 80; - } - - /** - * Returns browser name from browser configuration. - * - * @return string - */ - public function getBrowserName() - { - $browser_name = parent::getBrowserName(); - - return strlen($browser_name) ? $browser_name : 'chrome'; + return $this->getApiUsername() . ':' . $this->getApiKey() . '@ondemand.saucelabs.com'; } /** @@ -127,103 +58,14 @@ public function getDesiredCapabilities() $capabilities = parent::getDesiredCapabilities(); if ( !isset($capabilities['platform']) ) { - $capabilities['platform'] = 'Windows XP'; + $capabilities['platform'] = 'Windows 10'; } - if ( !isset($capabilities['version']) ) { - $capabilities['version'] = ''; + if ( !isset($capabilities['acceptInsecureCerts']) ) { + $capabilities['acceptInsecureCerts'] = true; } return $capabilities; } - /** - * Hook, called from "BrowserTestCase::setUp" method. - * - * @param BrowserTestCase $test_case Browser test case. - * - * @return self - */ - public function testSetUpHook(BrowserTestCase $test_case) - { - $desired_capabilities = $this->getDesiredCapabilities(); - - $desired_capabilities[SauceLabsCapability::NAME] = $this->getJobName($test_case); - - $jenkins_build_number = getenv('BUILD_NUMBER'); - - if ( $jenkins_build_number ) { - $desired_capabilities[SauceLabsCapability::BUILD] = $jenkins_build_number; - } - - $this->setDesiredCapabilities($desired_capabilities); - - return $this; - } - - /** - * Hook, called from "BrowserTestCase::run" method. - * - * @param BrowserTestCase $test_case Browser test case. - * @param \PHPUnit_Framework_TestResult $test_result Test result. - * - * @return self - */ - public function testAfterRunHook(BrowserTestCase $test_case, \PHPUnit_Framework_TestResult $test_result) - { - $passed = $this->getTestStatus($test_case, $test_result); - $this->getRestClient()->updateJob($this->getJobId($test_case), array('passed' => $passed)); - - return $this; - } - - /** - * Get Selenium2 current session id. - * - * @param BrowserTestCase $test_case Browser test case. - * - * @return string - * @throws \RuntimeException When test case session was created using an unsupported driver. - */ - protected function getJobId(BrowserTestCase $test_case) - { - $driver = $test_case->getSession()->getDriver(); - - if ( $driver instanceof Selenium2Driver ) { - $wd_session = $driver->getWebDriverSession(); - - return $wd_session ? basename($wd_session->getUrl()) : ''; - } - - throw new \RuntimeException('Unsupported session driver'); - } - - /** - * Returns Job name for "Sauce Labs" service. - * - * @param BrowserTestCase $test_case Browser test case. - * - * @return string - */ - protected function getJobName(BrowserTestCase $test_case) - { - if ( $this->isShared() ) { - return get_class($test_case); - } - - return $test_case->toString(); - } - - /** - * Returns API class for "Sauce Labs" service interaction. - * - * @return SauceRest - */ - protected function getRestClient() - { - $sauce = $this->getSauce(); - - return new SauceRest($sauce['username'], $sauce['api_key']); - } - } diff --git a/library/aik099/PHPUnit/BrowserSuite.php b/library/aik099/PHPUnit/BrowserSuite.php deleted file mode 100644 index 62da121..0000000 --- a/library/aik099/PHPUnit/BrowserSuite.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit; - - -/** - * Test Suite class for a set of tests from a single Test Case Class executed with a particular browser. - */ -class BrowserSuite extends TestSuiteBase -{ - - /** - * Create test suite based on given class name on browser configuration. - * - * @param string $class_name Class name. - * @param array $browser Browser configuration. - * - * @return self - */ - public static function fromClassAndBrowser($class_name, array $browser) - { - $suite = new static(); - - $name = 'undefined'; - $try_settings = array('alias', 'browserName', 'name'); - - foreach ($try_settings as $try_setting) { - if ( isset($browser[$try_setting]) ) { - $name = $browser[$try_setting]; - break; - } - } - - $suite->setName($class_name . ': ' . $name); - - return $suite; - } - - /** - * Sets given browser to be used in each underlying test cases and test suites. - * - * @param array $browser Browser configuration. - * - * @return self - */ - public function setBrowserFromConfiguration(array $browser) - { - /* @var $test BrowserTestCase */ - foreach ( $this->tests() as $test ) { - $test->setBrowserFromConfiguration($browser); - } - - return $this; - } - -} diff --git a/library/aik099/PHPUnit/BrowserTestCase.php b/library/aik099/PHPUnit/BrowserTestCase.php index 3dfc3f1..8a31340 100644 --- a/library/aik099/PHPUnit/BrowserTestCase.php +++ b/library/aik099/PHPUnit/BrowserTestCase.php @@ -12,19 +12,23 @@ use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; -use aik099\PHPUnit\Common\RemoteCoverage; -use aik099\PHPUnit\SessionStrategy\ISessionStrategy; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageTool; +use aik099\PHPUnit\Session\ISessionStrategy; +use aik099\PHPUnit\Session\SessionStrategyManager; +use aik099\PHPUnit\TestSuite\RegularTestSuite; use Behat\Mink\Exception\DriverException; use Behat\Mink\Session; +use ConsoleHelpers\PHPUnitCompat\AbstractTestCase; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; /** * Test Case class for writing browser-based tests. * - * @method \Mockery\Expectation shouldReceive + * @method \Mockery\Expectation shouldReceive(string $name) */ -abstract class BrowserTestCase extends \PHPUnit_Framework_TestCase +abstract class BrowserTestCase extends AbstractTestCase { /** @@ -34,12 +38,19 @@ abstract class BrowserTestCase extends \PHPUnit_Framework_TestCase */ public static $browsers = array(); + /** + * Browser configuration factory. + * + * @var IBrowserConfigurationFactory + */ + private $_browserConfigurationFactory; + /** * Remote coverage collection url. * * @var string Override to provide code coverage data from the server */ - protected $coverageScriptUrl; + private $_remoteCoverageScriptUrl; /** * Current browser configuration. @@ -60,14 +71,21 @@ abstract class BrowserTestCase extends \PHPUnit_Framework_TestCase * * @var SessionStrategyManager */ - protected $sessionStrategyManager; + private $_sessionStrategyManager; + + /** + * Remote coverage helper. + * + * @var RemoteCoverageHelper + */ + private $_remoteCoverageHelper; /** * Session strategy, used currently. * * @var ISessionStrategy */ - protected $sessionStrategy; + private $_sessionStrategy; /** * Test ID. @@ -76,6 +94,18 @@ abstract class BrowserTestCase extends \PHPUnit_Framework_TestCase */ private $_testId; + /** + * Sets application. + * + * @param IBrowserConfigurationFactory $browser_configuration_factory Browser configuration factory. + * + * @return void + */ + public function setBrowserConfigurationFactory(IBrowserConfigurationFactory $browser_configuration_factory) + { + $this->_browserConfigurationFactory = $browser_configuration_factory; + } + /** * Sets session strategy manager. * @@ -85,21 +115,46 @@ abstract class BrowserTestCase extends \PHPUnit_Framework_TestCase */ public function setSessionStrategyManager(SessionStrategyManager $session_strategy_manager) { - $this->sessionStrategyManager = $session_strategy_manager; + $this->_sessionStrategyManager = $session_strategy_manager; return $this; } /** - * Set session meta-info for "Sauce Labs". + * Sets remote coverage helper. + * + * @param RemoteCoverageHelper $remote_coverage_helper Remote coverage helper. * * @return void */ - protected function setUp() + public function setRemoteCoverageHelper(RemoteCoverageHelper $remote_coverage_helper) { - parent::setUp(); + $this->_remoteCoverageHelper = $remote_coverage_helper; + } + + /** + * Sets base url for remote coverage information collection. + * + * @param string $url URL. + * + * @return void + */ + protected function setRemoteCoverageScriptUrl($url) + { + $this->_remoteCoverageScriptUrl = $url; + } - $this->getBrowser()->testSetUpHook($this); + /** + * Set session meta-info for an API-based browser configurations. + * + * @return void + * @before + */ + protected function setUpTest() + { + if ( $this->_browser !== null ) { + $this->_browser->onTestSetup($this); + } } /** @@ -109,16 +164,16 @@ protected function setUp() * * @return self */ - public function setBrowser(BrowserConfiguration $browser) + protected function setBrowser(BrowserConfiguration $browser) { $this->_browser = $browser; - // configure session strategy - $session_strategy = $browser->getSessionStrategy(); - $session_strategy_hash = $browser->getSessionStrategyHash($this); - $browser_strategy = $this->sessionStrategyManager->getSessionStrategy($session_strategy, $session_strategy_hash); + // Configure session strategy. + $this->setSessionStrategy( + $this->_sessionStrategyManager->getSessionStrategy($browser, $this) + ); - return $this->setSessionStrategy($browser_strategy); + return $this; } /** @@ -127,7 +182,7 @@ public function setBrowser(BrowserConfiguration $browser) * @return BrowserConfiguration * @throws \RuntimeException When browser configuration isn't defined. */ - public function getBrowser() + protected function getBrowser() { if ( !is_object($this->_browser) ) { throw new \RuntimeException('Browser configuration not defined'); @@ -145,19 +200,21 @@ public function getBrowser() */ public function setBrowserFromConfiguration(array $browser_config) { - $browser_config = BrowserConfiguration::resolveAliases($browser_config, $this->getBrowserAliases()); - - // configure browser - if ( isset($browser_config['sauce']) ) { - $browser = new SauceLabsBrowserConfiguration($this->getBrowserAliases()); - } - else { - $browser = new BrowserConfiguration($this->getBrowserAliases()); - } - - $browser->setup($browser_config); + return $this->setBrowser( + $this->createBrowserConfiguration($browser_config) + ); + } - return $this->setBrowser($browser); + /** + * Returns browser configuration instance. + * + * @param array $browser_config Browser. + * + * @return BrowserConfiguration + */ + protected function createBrowserConfiguration(array $browser_config) + { + return $this->_browserConfigurationFactory->createBrowserConfiguration($browser_config, $this); } /** @@ -167,9 +224,9 @@ public function setBrowserFromConfiguration(array $browser_config) * * @return self */ - public function setSessionStrategy(ISessionStrategy $session_strategy = null) + protected function setSessionStrategy(ISessionStrategy $session_strategy = null) { - $this->sessionStrategy = $session_strategy; + $this->_sessionStrategy = $session_strategy; return $this; } @@ -180,24 +237,26 @@ public function setSessionStrategy(ISessionStrategy $session_strategy = null) * @return ISessionStrategy * @see setSessionStrategy() */ - public function getSessionStrategy() + protected function getSessionStrategy() { - if ( $this->sessionStrategy ) { - return $this->sessionStrategy; + if ( $this->_sessionStrategy !== null ) { + return $this->_sessionStrategy; } - // default session strategy (not session itself) shared across all test cases - return $this->sessionStrategyManager->getDefaultSessionStrategy(); + // Default session strategy (not session itself) shared across all test cases. + return $this->_sessionStrategyManager->getDefaultSessionStrategy(); } /** * Creates Mink session using current session strategy and returns it. * + * @param boolean $auto_create Automatically create session, when missing. + * * @return Session */ - public function getSession() + public function getSession($auto_create = true) { - if ( $this->_session && $this->_session->isStarted() ) { + if ( $this->_session || $auto_create === false ) { return $this->_session; } @@ -219,33 +278,25 @@ public function getSession() } /** - * Runs the test case and collects the results in a TestResult object. + * Collects remote coverage information and notifies strategy/browser about test finish. * - * If no TestResult object is passed a new one will be created. - * - * @param \PHPUnit_Framework_TestResult $result Test result. - * - * @return \PHPUnit_Framework_TestResult - * @throws \PHPUnit_Framework_Exception When exception was thrown during a test. + * @after */ - public function run(\PHPUnit_Framework_TestResult $result = null) + protected function tearDownTest() { - if ( $result === null ) { - $result = $this->createResult(); - } - - parent::run($result); + $result = $this->getTestResultObject(); - if ( $result->getCollectCodeCoverageInformation() ) { + if ( $this->getCollectCodeCoverageInformation() ) { $result->getCodeCoverage()->append($this->getRemoteCodeCoverageInformation(), $this); } - $this->getBrowser()->testAfterRunHook($this, $result); - - // do not call this before to give the time to the Listeners to run - $this->getSessionStrategy()->endOfTest($this->_session); + if ( $this->_browser !== null ) { + $this->_browser->onTestEnded($this, $result); + } - return $result; + if ( $this->_sessionStrategy !== null ) { + $this->_sessionStrategy->onTestEnded($this); + } } /** @@ -254,8 +305,12 @@ public function run(\PHPUnit_Framework_TestResult $result = null) * @return boolean * @throws \RuntimeException When used before test is started. */ - public function getCollectCodeCoverageInformation() + protected function getCollectCodeCoverageInformation() { + if ( !$this->_remoteCoverageScriptUrl ) { + return false; + } + $result = $this->getTestResultObject(); if ( !is_object($result) ) { @@ -269,7 +324,6 @@ public function getCollectCodeCoverageInformation() * Override to tell remote website, that code coverage information needs to be collected. * * @return mixed - * @throws \Exception When exception was thrown inside the test. */ protected function runTest() { @@ -277,8 +331,8 @@ protected function runTest() $this->_testId = get_class($this) . '__' . $this->getName(); $session = $this->getSession(); - $session->setCookie('PHPUNIT_SELENIUM_TEST_ID', null); - $session->setCookie('PHPUNIT_SELENIUM_TEST_ID', $this->_testId); + $session->setCookie(RemoteCoverageTool::TEST_ID_VARIABLE, null); + $session->setCookie(RemoteCoverageTool::TEST_ID_VARIABLE, $this->_testId); } return parent::runTest(); @@ -289,23 +343,23 @@ protected function runTest() * * @return self */ - public function endOfTestCase() + public function onTestSuiteEnded() { - $this->getSessionStrategy()->endOfTestCase($this->_session); + if ( $this->_sessionStrategy !== null ) { + $this->_sessionStrategy->onTestSuiteEnded($this); + } return $this; } /** - * Returns remote code coverage information. + * Returns remote code coverage information, when enabled. * - * @return array + * @return array|RawCodeCoverageData */ - public function getRemoteCodeCoverageInformation() + protected function getRemoteCodeCoverageInformation() { - $remote_coverage = new RemoteCoverage($this->coverageScriptUrl, $this->_testId); - - return $remote_coverage->get(); + return $this->_remoteCoverageHelper->get($this->_remoteCoverageScriptUrl, $this->_testId); } /** @@ -313,25 +367,23 @@ public function getRemoteCodeCoverageInformation() * * @param string $class_name Test case class name. * - * @return TestSuite + * @return RegularTestSuite */ public static function suite($class_name) { - return TestSuite::fromTestCaseClass($class_name); + $application = Application::getInstance(); + + return $application->getTestSuiteFactory()->createSuiteFromTestCase($class_name); } /** - * This method is called when a test method did not execute successfully. - * - * @param \Exception $e Exception. - * - * @return void + * @inheritDoc */ - protected function onNotSuccessfulTest(\Exception $e) + protected function onNotSuccessfulTestCompat($e) { - $this->getSessionStrategy()->notSuccessfulTest($e); - - parent::onNotSuccessfulTest($e); + if ( $this->_sessionStrategy !== null ) { + $this->_sessionStrategy->onTestFailed($this, $e); + } } /** diff --git a/library/aik099/PHPUnit/Common/RemoteCoverage.php b/library/aik099/PHPUnit/Common/RemoteCoverage.php deleted file mode 100644 index 4156100..0000000 --- a/library/aik099/PHPUnit/Common/RemoteCoverage.php +++ /dev/null @@ -1,134 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit\Common; - - -/** - * Class collects remove code coverage information and maps patch from remote to local server. - * - * @method \Mockery\Expectation shouldReceive - */ -class RemoteCoverage -{ - - /** - * Url to a script, that will provide remote coverage information. - * - * @var string - */ - private $_coverageScriptUrl; - - /** - * ID of test, that to look for. - * - * @var string - */ - private $_testId; - - /** - * Creates an instance of remote coverage class. - * - * @param string $coverage_script_url Coverage script irl. - * @param string $test_id Test ID. - * - * @throws \InvalidArgumentException When empty coverage script url given. - */ - public function __construct($coverage_script_url, $test_id) - { - if ( empty($coverage_script_url) ) { - throw new \InvalidArgumentException('Coverage script url is empty'); - } - - $this->_coverageScriptUrl = $coverage_script_url; - $this->_testId = $test_id; - } - - /** - * Returns raw remote coverage information. - * - * @return string - */ - public function getFetchUrl() - { - return sprintf('%s?PHPUNIT_SELENIUM_TEST_ID=%s', $this->_coverageScriptUrl, $this->_testId); - } - - /** - * Retrieves remote coverage information. - * - * @return array - * @throws \Exception When no data was retrieved. - */ - public function get() - { - $url = $this->getFetchUrl(); - $buffer = file_get_contents($url); - - if ( $buffer !== false ) { - $coverage_data = unserialize($buffer); - - if ( is_array($coverage_data) ) { - return $this->matchLocalAndRemotePaths($coverage_data); - } - - throw new \RuntimeException(sprintf('Empty or invalid code coverage data received from url "%s"', $url)); - } - - return array(); - } - - /** - * Returns only files from remote server, that are matching files on test machine. - * - * @param array $coverage Remote coverage information. - * - * @return array - * @author Mattis Stordalen Flister - */ - protected function matchLocalAndRemotePaths(array $coverage) - { - $coverage_with_local_paths = array(); - - foreach ($coverage as $original_remote_path => $data) { - $remote_path = $original_remote_path; - $separator = $this->findDirectorySeparator($remote_path); - - while ( !($local_path = stream_resolve_include_path($remote_path)) && - strpos($remote_path, $separator) !== false ) { - $remote_path = substr($remote_path, strpos($remote_path, $separator) + 1); - } - - if ( $local_path && md5_file($local_path) == $data['md5'] ) { - $coverage_with_local_paths[$local_path] = $data['coverage']; - } - } - - return $coverage_with_local_paths; - } - - /** - * Returns path separator in given path. - * - * @param string $path Path to file. - * - * @return string - * @author Mattis Stordalen Flister - */ - protected function findDirectorySeparator($path) - { - if ( strpos($path, '/') !== false ) { - return '/'; - } - - return '\\'; - } - -} diff --git a/library/aik099/PHPUnit/Common/append.php b/library/aik099/PHPUnit/Common/append.php deleted file mode 100644 index 172d404..0000000 --- a/library/aik099/PHPUnit/Common/append.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -/** - * PHPUnit - * - * Copyright (c) 2010-2013, Sebastian Bergmann . - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Sebastian Bergmann nor the names of his - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package PHPUnit_Selenium - * @author Sebastian Bergmann - * @copyright 2010-2013 Sebastian Bergmann - * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License - * @link http://www.phpunit.de/ - * @since File available since Release 1.0.0 - */ - -if ( isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && - !isset($_GET['PHPUNIT_SELENIUM_TEST_ID']) && - extension_loaded('xdebug') -) { - $GLOBALS['PHPUNIT_FILTERED_FILES'][] = __FILE__; - - $data = xdebug_get_code_coverage(); - xdebug_stop_code_coverage(); - - foreach ($GLOBALS['PHPUNIT_FILTERED_FILES'] as $file) { - unset($data[$file]); - } - - if ( is_string($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']) && - is_dir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']) - ) { - $file = $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] . - DIRECTORY_SEPARATOR . md5($_SERVER['SCRIPT_FILENAME']); - } - else { - $file = $_SERVER['SCRIPT_FILENAME']; - } - - file_put_contents( - $name = $file . '.' . md5(uniqid(rand(), true)) . '.' . $_COOKIE['PHPUNIT_SELENIUM_TEST_ID'], - serialize($data) - ); -} diff --git a/library/aik099/PHPUnit/Common/phpunit_coverage.php b/library/aik099/PHPUnit/Common/phpunit_coverage.php deleted file mode 100644 index f2031ca..0000000 --- a/library/aik099/PHPUnit/Common/phpunit_coverage.php +++ /dev/null @@ -1,100 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -/** - * PHPUnit - * - * Copyright (c) 2010-2013, Sebastian Bergmann . - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Sebastian Bergmann nor the names of his - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package PHPUnit_Selenium - * @author Sebastian Bergmann - * @copyright 2010-2013 Sebastian Bergmann - * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License - * @link http://www.phpunit.de/ - * @since File available since Release 1.0.0 - */ - -require_once 'File/Iterator/Autoload.php'; -require_once 'PHP/CodeCoverage/Autoload.php'; - -// Set this to the directory that contains the code coverage files. -// It defaults to getcwd(). If you have configured a different directory -// in prepend.php, you need to configure the same directory here. - -if ( !isset($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']) ) { - $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = getcwd(); -} - -if ( isset($_GET['PHPUNIT_SELENIUM_TEST_ID']) ) { - $facade = new File_Iterator_Facade(); - - $files = $facade->getFilesAsArray( - $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'], - $_GET['PHPUNIT_SELENIUM_TEST_ID'] - ); - - $coverage = array(); - - foreach ($files as $file) { - $data = unserialize(file_get_contents($file)); - unlink($file); - unset($file); - $filter = new PHP_CodeCoverage_Filter(); - - foreach ($data as $file => $lines) { - if ( $filter->isFile($file) ) { - if ( !isset($coverage[$file]) ) { - $coverage[$file] = array('md5' => md5_file($file), 'coverage' => $lines); - } - else { - foreach ($lines as $line => $flag) { - if ( !isset($coverage[$file]['coverage'][$line]) || - $flag > $coverage[$file]['coverage'][$line] - ) { - $coverage[$file]['coverage'][$line] = $flag; - } - } - } - } - } - } - - print serialize($coverage); -} diff --git a/library/aik099/PHPUnit/Common/prepend.php b/library/aik099/PHPUnit/Common/prepend.php deleted file mode 100644 index 05171b6..0000000 --- a/library/aik099/PHPUnit/Common/prepend.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -/** - * PHPUnit - * - * Copyright (c) 2010-2013, Sebastian Bergmann . - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Sebastian Bergmann nor the names of his - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package PHPUnit_Selenium - * @author Sebastian Bergmann - * @copyright 2010-2013 Sebastian Bergmann - * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License - * @link http://www.phpunit.de/ - * @since File available since Release 1.0.0 - */ - -// By default the code coverage files are written to the same directory -// that contains the covered sourcecode files. Use this setting to change -// the default behaviour and set a specific directory to write the files to. -// If you change the default setting, please make sure to also configure -// the same directory in phpunit_coverage.php. Also note that the webserver -// needs write access to the directory. - -if ( !isset($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']) ) { - $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = false; -} - -if ( isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && - !isset($_GET['PHPUNIT_SELENIUM_TEST_ID']) && - extension_loaded('xdebug') -) { - $GLOBALS['PHPUNIT_FILTERED_FILES'] = array(__FILE__); - - xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); -} diff --git a/library/aik099/PHPUnit/DIContainer.php b/library/aik099/PHPUnit/DIContainer.php new file mode 100644 index 0000000..bb73c66 --- /dev/null +++ b/library/aik099/PHPUnit/DIContainer.php @@ -0,0 +1,145 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfigurationFactory; +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use aik099\PHPUnit\MinkDriver\GoutteDriverFactory; +use aik099\PHPUnit\MinkDriver\SahiDriverFactory; +use aik099\PHPUnit\MinkDriver\Selenium2DriverFactory; +use aik099\PHPUnit\MinkDriver\WebdriverClassicFactory; +use aik099\PHPUnit\MinkDriver\ZombieDriverFactory; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\RemoteCoverage\RemoteUrl; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use aik099\PHPUnit\Session\IsolatedSessionStrategy; +use aik099\PHPUnit\Session\SessionStrategyFactory; +use aik099\PHPUnit\Session\SessionStrategyManager; +use aik099\PHPUnit\Session\SharedSessionStrategy; +use aik099\PHPUnit\TestSuite\BrowserTestSuite; +use aik099\PHPUnit\TestSuite\RegularTestSuite; +use aik099\PHPUnit\TestSuite\TestSuiteFactory; +use PimpleCopy\Pimple\Container; + +class DIContainer extends Container implements IApplicationAware +{ + + /** + * Sets application. + * + * @param Application $application The application. + * + * @return void + */ + public function setApplication(Application $application) + { + $this['application'] = $application; + } + + /** + * Instantiate the container. + * + * Objects and parameters can be passed as argument to the constructor. + * + * @param array $values The parameters or objects. + */ + public function __construct(array $values = array()) + { + parent::__construct($values); + + $this['session_strategy_factory'] = function ($c) { + $session_strategy_factory = new SessionStrategyFactory(); + + $session_strategy_factory->register( + ISessionStrategyFactory::TYPE_ISOLATED, + new IsolatedSessionStrategy() + ); + + $session_strategy_factory->register( + ISessionStrategyFactory::TYPE_SHARED, + new SharedSessionStrategy( + new IsolatedSessionStrategy() + ) + ); + + return $session_strategy_factory; + }; + + $this['session_strategy_manager'] = function ($c) { + return new SessionStrategyManager($c['session_strategy_factory']); + }; + + $this['remote_url'] = function () { + return new RemoteUrl(); + }; + + $this['remote_coverage_helper'] = function ($c) { + return new RemoteCoverageHelper($c['remote_url']); + }; + + $this['test_suite_factory'] = function ($c) { + $test_suite_factory = new TestSuiteFactory( + $c['session_strategy_manager'], + $c['browser_configuration_factory'], + $c['remote_coverage_helper'] + ); + $test_suite_factory->setApplication($c['application']); + + return $test_suite_factory; + }; + + $this['regular_test_suite'] = $this->factory(function ($c) { + return new RegularTestSuite(); + }); + + $this['browser_test_suite'] = $this->factory(function ($c) { + return new BrowserTestSuite(); + }); + + $this['driver_factory_registry'] = function () { + $registry = new DriverFactoryRegistry(); + + $registry->add(new Selenium2DriverFactory()); + $registry->add(new WebdriverClassicFactory()); + $registry->add(new SahiDriverFactory()); + $registry->add(new GoutteDriverFactory()); + $registry->add(new ZombieDriverFactory()); + + return $registry; + }; + + $this['api_client_factory'] = function ($c) { + return new APIClientFactory(); + }; + + $this['browser_configuration_factory'] = function ($c) { + $browser_configuration_factory = new BrowserConfigurationFactory(); + + $browser_configuration_factory->register( + new BrowserConfiguration($c['driver_factory_registry']) + ); + $browser_configuration_factory->register( + new SauceLabsBrowserConfiguration($c['driver_factory_registry'], $c['api_client_factory']) + ); + $browser_configuration_factory->register( + new BrowserStackBrowserConfiguration($c['driver_factory_registry'], $c['api_client_factory']) + ); + + return $browser_configuration_factory; + }; + } + +} diff --git a/library/aik099/PHPUnit/IApplicationAware.php b/library/aik099/PHPUnit/IApplicationAware.php new file mode 100644 index 0000000..19e92f3 --- /dev/null +++ b/library/aik099/PHPUnit/IApplicationAware.php @@ -0,0 +1,29 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit; + + +/** + * Interface to indicate that test case or test suite class understands Application. + */ +interface IApplicationAware +{ + + /** + * Sets application. + * + * @param Application $application The application. + * + * @return void + */ + public function setApplication(Application $application); + +} diff --git a/library/aik099/PHPUnit/MinkDriver/AbstractDriverFactory.php b/library/aik099/PHPUnit/MinkDriver/AbstractDriverFactory.php new file mode 100644 index 0000000..efb0a6b --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/AbstractDriverFactory.php @@ -0,0 +1,39 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +abstract class AbstractDriverFactory implements IMinkDriverFactory +{ + + /** + * Throws an exception with driver installation instructions. + * + * @param string $class_name Driver class name. + * + * @return void + * @throws \RuntimeException When driver isn't installed. + */ + protected function assertInstalled($class_name) + { + if ( !class_exists($class_name) ) { + throw new \RuntimeException( + sprintf( + 'The "%s" driver package is not installed. Please follow installation instructions at %s.', + $this->getDriverName(), + $this->getDriverPackageUrl() + ) + ); + } + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/DriverFactoryRegistry.php b/library/aik099/PHPUnit/MinkDriver/DriverFactoryRegistry.php new file mode 100644 index 0000000..03336f7 --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/DriverFactoryRegistry.php @@ -0,0 +1,68 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +class DriverFactoryRegistry +{ + + /** + * Driver factory registry. + * + * @var IMinkDriverFactory[] + */ + private $_registry = array(); + + /** + * Registers Mink driver factory. + * + * @param IMinkDriverFactory $driver_factory Driver factory. + * + * @return void + * @throws \LogicException When driver factory is already registered. + */ + public function add(IMinkDriverFactory $driver_factory) + { + $driver_name = $driver_factory->getDriverName(); + + if ( isset($this->_registry[$driver_name]) ) { + throw new \LogicException('Driver factory for "' . $driver_name . '" driver is already registered.'); + } + + $this->_registry[$driver_name] = $driver_factory; + } + + /** + * Looks up driver factory by name of the driver it can create. + * + * @param string $driver_name Driver name. + * + * @return IMinkDriverFactory + * @throws \OutOfBoundsException When driver not found. + */ + public function get($driver_name) + { + if ( !isset($this->_registry[$driver_name]) ) { + $error_msg = 'The "' . $driver_name . '" driver is unknown.'; + + if ( $this->_registry ) { + $drivers = '"' . implode('", "', array_keys($this->_registry)) . '"'; + $error_msg .= ' Please instead use any of these supported drivers: ' . $drivers . '.'; + } + + throw new \OutOfBoundsException($error_msg); + } + + return $this->_registry[$driver_name]; + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/GoutteDriverFactory.php b/library/aik099/PHPUnit/MinkDriver/GoutteDriverFactory.php new file mode 100644 index 0000000..c68438e --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/GoutteDriverFactory.php @@ -0,0 +1,154 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; + +class GoutteDriverFactory extends AbstractDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName() + { + return 'goutte'; + } + + /** + * @inheritDoc + */ + public function getDriverPackageUrl() + { + return 'https://packagist.org/packages/behat/mink-goutte-driver'; + } + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults() + { + return array( + 'driverOptions' => array( + 'server_parameters' => array(), + 'guzzle_parameters' => array(), + ), + ); + } + + /** + * @inheritDoc + */ + public function createDriver(BrowserConfiguration $browser) + { + $this->assertInstalled('Behat\Mink\Driver\GoutteDriver'); + + $driver_options = $browser->getDriverOptions(); + + if ( $this->_isGoutte1() ) { + $guzzle_client = $this->_buildGuzzle3Client($driver_options['guzzle_parameters']); + } + elseif ( $this->_isGuzzle6() ) { + $guzzle_client = $this->_buildGuzzle6Client($driver_options['guzzle_parameters']); + } + else { + $guzzle_client = $this->_buildGuzzle4Client($driver_options['guzzle_parameters']); + } + + $goutte_client = new \Behat\Mink\Driver\Goutte\Client($driver_options['server_parameters']); + $goutte_client->setClient($guzzle_client); + + return new \Behat\Mink\Driver\GoutteDriver($goutte_client); + } + + /** + * Builds Guzzle 6 client. + * + * @param array $parameters Parameters. + * + * @return \GuzzleHttp\Client + */ + private function _buildGuzzle6Client(array $parameters) + { + // Force the parameters set by default in Goutte to reproduce its behavior. + $parameters['allow_redirects'] = false; + $parameters['cookies'] = true; + + return new \GuzzleHttp\Client($parameters); + + } + + /** + * Builds Guzzle 4 client. + * + * @param array $parameters Parameters. + * + * @return \GuzzleHttp\Client + */ + private function _buildGuzzle4Client(array $parameters) + { + // Force the parameters set by default in Goutte to reproduce its behavior. + $parameters['allow_redirects'] = false; + $parameters['cookies'] = true; + + return new \GuzzleHttp\Client(array('defaults' => $parameters)); + + } + + /** + * Builds Guzzle 3 client. + * + * @param array $parameters Parameters. + * + * @return \Guzzle\Http\Client + */ + private function _buildGuzzle3Client(array $parameters) + { + // Force the parameters set by default in Goutte to reproduce its behavior. + $parameters['redirect.disable'] = true; + + return new \Guzzle\Http\Client(null, $parameters); + } + + /** + * Determines Goutte client version. + * + * @return boolean + */ + private function _isGoutte1() + { + $reflection = new \ReflectionParameter(array('Goutte\Client', 'setClient'), 0); + + if ( $reflection->getClass() && 'Guzzle\Http\ClientInterface' === $reflection->getClass()->getName() ) { + return true; + } + + return false; + } + + /** + * Determines Guzzle version. + * + * @return boolean + */ + private function _isGuzzle6() + { + return interface_exists('GuzzleHttp\ClientInterface') && + version_compare(\GuzzleHttp\ClientInterface::VERSION, '6.0.0', '>='); + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/IMinkDriverFactory.php b/library/aik099/PHPUnit/MinkDriver/IMinkDriverFactory.php new file mode 100644 index 0000000..dadce1d --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/IMinkDriverFactory.php @@ -0,0 +1,52 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use Behat\Mink\Driver\DriverInterface; + +interface IMinkDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName(); + + /** + * Returns driver package URL. + * + * @return string + */ + public function getDriverPackageUrl(); + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults(); + + /** + * Returns a new driver instance according to the browser configuration. + * + * @param BrowserConfiguration $browser The browser configuration. + * + * @return DriverInterface + * @throws \RuntimeException When driver isn't installed. + */ + public function createDriver(BrowserConfiguration $browser); + +} diff --git a/library/aik099/PHPUnit/MinkDriver/SahiDriverFactory.php b/library/aik099/PHPUnit/MinkDriver/SahiDriverFactory.php new file mode 100644 index 0000000..5fa3cd0 --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/SahiDriverFactory.php @@ -0,0 +1,78 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; + +class SahiDriverFactory extends AbstractDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName() + { + return 'sahi'; + } + + /** + * @inheritDoc + */ + public function getDriverPackageUrl() + { + return 'https://packagist.org/packages/behat/mink-sahi-driver'; + } + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults() + { + return array( + 'port' => 9999, + 'driverOptions' => array( + 'sid' => null, + 'limit' => 600, + 'browser' => null, + ), + ); + } + + /** + * @inheritDoc + */ + public function createDriver(BrowserConfiguration $browser) + { + $this->assertInstalled('Behat\Mink\Driver\SahiDriver'); + + $driver_options = $browser->getDriverOptions(); + + $connection = new \Behat\SahiClient\Connection( + $driver_options['sid'], + $browser->getHost(), + $browser->getPort(), + $driver_options['browser'], + $driver_options['limit'] + ); + + return new \Behat\Mink\Driver\SahiDriver( + $browser->getBrowserName(), + new \Behat\SahiClient\Client($connection) + ); + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/Selenium2DriverFactory.php b/library/aik099/PHPUnit/MinkDriver/Selenium2DriverFactory.php new file mode 100644 index 0000000..7667fe8 --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/Selenium2DriverFactory.php @@ -0,0 +1,74 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; + +class Selenium2DriverFactory extends AbstractDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName() + { + return 'selenium2'; + } + + /** + * @inheritDoc + */ + public function getDriverPackageUrl() + { + return 'https://packagist.org/packages/behat/mink-selenium2-driver'; + } + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults() + { + return array( + 'port' => 4444, + 'driverOptions' => array(), + ); + } + + /** + * @inheritDoc + */ + public function createDriver(BrowserConfiguration $browser) + { + $this->assertInstalled('Behat\Mink\Driver\Selenium2Driver'); + + $browser_name = $browser->getBrowserName(); + $capabilities = $browser->getDesiredCapabilities(); + $capabilities['browserName'] = $browser_name; + + // TODO: Maybe doesn't work! + ini_set('default_socket_timeout', $browser->getTimeout()); + + $driver = new \Behat\Mink\Driver\Selenium2Driver( + $browser_name, + $capabilities, + 'http://' . $browser->getHost() . ':' . $browser->getPort() . '/wd/hub' + ); + + return $driver; + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/WebdriverClassicFactory.php b/library/aik099/PHPUnit/MinkDriver/WebdriverClassicFactory.php new file mode 100644 index 0000000..e528bbf --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/WebdriverClassicFactory.php @@ -0,0 +1,74 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; + +class WebdriverClassicFactory extends AbstractDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName() + { + return 'webdriver-classic'; + } + + /** + * @inheritDoc + */ + public function getDriverPackageUrl() + { + return 'https://packagist.org/packages/mink/webdriver-classic-driver'; + } + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults() + { + return array( + 'port' => 4444, + 'driverOptions' => array(), + ); + } + + /** + * @inheritDoc + */ + public function createDriver(BrowserConfiguration $browser) + { + $this->assertInstalled('Mink\WebdriverClassicDriver\WebdriverClassicDriver'); + + $browser_name = $browser->getBrowserName(); + $capabilities = $browser->getDesiredCapabilities(); + $capabilities['browserName'] = $browser_name; + + // TODO: Maybe doesn't work! + ini_set('default_socket_timeout', $browser->getTimeout()); + + $driver = new \Mink\WebdriverClassicDriver\WebdriverClassicDriver( + $browser_name, + $capabilities, + 'http://' . $browser->getHost() . ':' . $browser->getPort() . '/wd/hub' + ); + + return $driver; + } + +} diff --git a/library/aik099/PHPUnit/MinkDriver/ZombieDriverFactory.php b/library/aik099/PHPUnit/MinkDriver/ZombieDriverFactory.php new file mode 100644 index 0000000..59bef48 --- /dev/null +++ b/library/aik099/PHPUnit/MinkDriver/ZombieDriverFactory.php @@ -0,0 +1,77 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; + +class ZombieDriverFactory extends AbstractDriverFactory +{ + + /** + * Returns driver name, that can be used in browser configuration. + * + * @return string + */ + public function getDriverName() + { + return 'zombie'; + } + + /** + * @inheritDoc + */ + public function getDriverPackageUrl() + { + return 'https://packagist.org/packages/behat/mink-zombie-driver'; + } + + /** + * Returns default values for browser configuration. + * + * @return array + */ + public function getDriverDefaults() + { + return array( + 'port' => 8124, + 'driverOptions' => array( + 'node_bin' => 'node', + 'server_path' => null, + 'threshold' => 2000000, + 'node_modules_path' => '', + ), + ); + } + + /** + * @inheritDoc + */ + public function createDriver(BrowserConfiguration $browser) + { + $this->assertInstalled('Behat\Mink\Driver\ZombieDriver'); + + $driver_options = $browser->getDriverOptions(); + + return new \Behat\Mink\Driver\ZombieDriver( + new \Behat\Mink\Driver\NodeJS\Server\ZombieServer( + $browser->getHost(), + $browser->getPort(), + $driver_options['node_bin'], + $driver_options['server_path'], + $driver_options['threshold'], + $driver_options['node_modules_path'] + ) + ); + } + +} diff --git a/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelper.php b/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelper.php new file mode 100644 index 0000000..0620181 --- /dev/null +++ b/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelper.php @@ -0,0 +1,171 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\RemoteCoverage; + + +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * Class collects remove code coverage information and maps patch from remote to local server. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class RemoteCoverageHelper +{ + + /** + * Remote URL. + * + * @var RemoteUrl + */ + private $_remoteUrl; + + /** + * Creates an instance of remote coverage class. + * + * @param RemoteUrl $remote_url Remote URL. + */ + public function __construct(RemoteUrl $remote_url) + { + $this->_remoteUrl = $remote_url; + } + + /** + * Retrieves remote coverage information. + * + * @param string $coverage_script_url Coverage script irl. + * @param string $test_id Test ID. + * + * @throws \RuntimeException Broken code coverage retrieved. + * @return array + */ + public function get($coverage_script_url, $test_id) + { + $url = $this->createUrl($coverage_script_url, $test_id); + $buffer = $this->_remoteUrl->getPageContent($url); + + if ( $buffer !== false ) { + $coverage_data = unserialize($buffer); + + if ( is_array($coverage_data) || \is_object($coverage_data) ) { + return $this->upgradeCoverageDataFormat( + $this->matchLocalAndRemotePaths($coverage_data) + ); + } + + throw new \RuntimeException('Empty or invalid code coverage data received from url "' . $url . '"'); + } + + return $this->getEmpty(); + } + + /** + * Returns an empty coverage data. + * + * @return array|RawCodeCoverageData + */ + public function getEmpty() + { + return $this->upgradeCoverageDataFormat(array()); + } + + /** + * Upgrades coverage data format. + * + * @param array|RawCodeCoverageData $coverage_data Coverage data. + * + * @return array|RawCodeCoverageData + */ + protected function upgradeCoverageDataFormat($coverage_data) + { + // @codeCoverageIgnoreStart + if ( !\class_exists('\SebastianBergmann\CodeCoverage\RawCodeCoverageData') || !\is_array($coverage_data) ) { + return $coverage_data; + } + // @codeCoverageIgnoreEnd + + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($coverage_data); + } + + /** + * Returns url for remote code coverage collection. + * + * @param string $coverage_script_url Coverage script irl. + * @param string $test_id Test ID. + * + * @return string + * @throws \InvalidArgumentException When empty coverage script url given. + */ + protected function createUrl($coverage_script_url, $test_id) + { + if ( !$coverage_script_url || !$test_id ) { + throw new \InvalidArgumentException('Both Coverage script URL and Test ID must be filled in'); + } + + $query_string = array( + 'rct_mode' => 'output', + RemoteCoverageTool::TEST_ID_VARIABLE => $test_id, + ); + + $url = $coverage_script_url; + $url .= strpos($url, '?') === false ? '?' : '&'; + $url .= http_build_query($query_string); + + return $url; + } + + /** + * Returns only files from remote server, that are matching files on test machine. + * + * @param array $coverage Remote coverage information. + * + * @return array + * @author Mattis Stordalen Flister + */ + protected function matchLocalAndRemotePaths(array $coverage) + { + $coverage_with_local_paths = array(); + + foreach ( $coverage as $original_remote_path => $data ) { + $remote_path = $original_remote_path; + $separator = $this->findDirectorySeparator($remote_path); + + while ( !($local_path = stream_resolve_include_path($remote_path)) && + strpos($remote_path, $separator) !== false ) { + $remote_path = substr($remote_path, strpos($remote_path, $separator) + 1); + } + + if ( $local_path && md5_file($local_path) == $data['md5'] ) { + $coverage_with_local_paths[$local_path] = $data['coverage']; + } + } + + return $coverage_with_local_paths; + } + + /** + * Returns path separator in given path. + * + * @param string $path Path to file. + * + * @return string + * @author Mattis Stordalen Flister + */ + protected function findDirectorySeparator($path) + { + if ( strpos($path, '/') !== false ) { + return '/'; + } + + return '\\'; + } + +} diff --git a/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageTool.php b/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageTool.php new file mode 100644 index 0000000..6ce7b80 --- /dev/null +++ b/library/aik099/PHPUnit/RemoteCoverage/RemoteCoverageTool.php @@ -0,0 +1,225 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\RemoteCoverage; + + +/* Include file from PEAR. +require_once 'File/Iterator/Autoload.php'; +require_once 'PHP/CodeCoverage/Autoload.php';*/ + +class RemoteCoverageTool +{ + + const TEST_ID_VARIABLE = 'PHPUNIT_MINK_TEST_ID'; + + const DATA_DIRECTORY_VARIABLE = 'PHPUNIT_MINK_COVERAGE_DATA_DIRECTORY'; + + /** + * Directory for coverage information collection. + * + * @var string + */ + protected $dataDirectory; + + /** + * Files, excluded from coverage collection. + * + * @var array + */ + protected $excludedFiles = array(__FILE__); + + /** + * Collects & reports coverage information. + * + * @param string|null $data_directory Directory for coverage information collection. + * + * @return void + */ + public static function init($data_directory = null) + { + $coverage_tool = new self($data_directory); + $mode = isset($_GET['rct_mode']) ? $_GET['rct_mode'] : ''; + + if ( $mode == 'output' ) { + echo $coverage_tool->aggregateCoverageInformation(); + } + else { + $coverage_tool->startCollection(); + register_shutdown_function(array($coverage_tool, 'stopCollection')); + } + } + + /** + * Creates an instance of remove coverage tool. + * + * @param string|null $data_directory Directory for coverage information collection. + */ + public function __construct($data_directory = null) + { + if ( !isset($data_directory) ) { + if ( isset($GLOBALS[self::DATA_DIRECTORY_VARIABLE]) ) { + $this->dataDirectory = $this->assertDirectory($GLOBALS[self::DATA_DIRECTORY_VARIABLE]); + } + else { + $this->dataDirectory = getcwd(); + } + } + else { + $this->dataDirectory = $this->assertDirectory($data_directory); + } + } + + /** + * Checks that a directory is valid. + * + * @param string $directory Directory. + * + * @throws \InvalidArgumentException When directory is invalid. + * @return string + */ + protected function assertDirectory($directory) + { + if ( !is_string($directory) || !is_dir($directory) || !file_exists($directory) ) { + throw new \InvalidArgumentException('Directory "' . $directory . '" is invalid'); + } + + return $directory; + } + + /** + * Excludes a file from coverage. + * + * @param string $file Path to file, that needs to be excluded. + * + * @return void + */ + public function excludeFile($file) + { + $this->excludedFiles[] = $file; + } + + /** + * Starts coverage information collection. + * + * @return void + */ + public function startCollection() + { + if ( !$this->enabled() ) { + return; + } + + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } + + /** + * Stops coverage information collection. + * + * @return void + */ + public function stopCollection() + { + if ( !$this->enabled() ) { + return; + } + + $data = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + + foreach ( $this->excludedFiles as $file ) { + unset($data[$file]); + } + + $unique_id = md5(uniqid(rand(), true)); + file_put_contents( + $name = $this->getStorageLocationPrefix() . '.' . $unique_id . '.' . $_COOKIE[self::TEST_ID_VARIABLE], + serialize($data) + ); + } + + /** + * Determines if coverage information collection can be started. + * + * @return string + * @throws \RuntimeException When Xdebug extension not enabled. + */ + protected function enabled() + { + if ( !extension_loaded('xdebug') ) { + throw new \RuntimeException('Xdebug extension must be enabled for coverage collection'); + } + + return isset($_COOKIE[self::TEST_ID_VARIABLE]) && !isset($_GET[self::TEST_ID_VARIABLE]); + } + + /** + * Returns name of the file, where coverage information will be stored. + * + * @return string + */ + protected function getStorageLocationPrefix() + { + return $this->dataDirectory . DIRECTORY_SEPARATOR . md5($_SERVER['SCRIPT_FILENAME']); + } + + /** + * Aggregates previously collected coverage information. + * + * @return string + */ + public function aggregateCoverageInformation() + { + if ( !isset($_GET[self::TEST_ID_VARIABLE]) ) { + return ''; + } + + $coverage = array(); + $filter = new \PHP_CodeCoverage_Filter(); + + foreach ( $this->getDataDirectoryFiles() as $data_directory_file ) { + $raw_coverage_data = unserialize(file_get_contents($data_directory_file)); + + foreach ( $raw_coverage_data as $file => $lines ) { + if ( !$filter->isFile($file) ) { + continue; + } + + if ( !isset($coverage[$file]) ) { + $coverage[$file] = array('md5' => md5_file($file), 'coverage' => $lines); + } + else { + foreach ( $lines as $line => $flag ) { + if ( !isset($coverage[$file]['coverage'][$line]) + || $flag > $coverage[$file]['coverage'][$line] + ) { + $coverage[$file]['coverage'][$line] = $flag; + } + } + } + } + } + + return serialize($coverage); + } + + /** + * Returns contents of data directory for a current test. + * + * @return array + */ + protected function getDataDirectoryFiles() + { + $facade = new \File_Iterator_Facade(); + + return $facade->getFilesAsArray($this->dataDirectory, $_GET[self::TEST_ID_VARIABLE]); + } + +} diff --git a/library/aik099/PHPUnit/RemoteCoverage/RemoteUrl.php b/library/aik099/PHPUnit/RemoteCoverage/RemoteUrl.php new file mode 100644 index 0000000..34a2350 --- /dev/null +++ b/library/aik099/PHPUnit/RemoteCoverage/RemoteUrl.php @@ -0,0 +1,34 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\RemoteCoverage; + + +/** + * Class makes request to remote server and returns url. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class RemoteUrl +{ + + /** + * Returns content of the page from given URL. + * + * @param string $url Page URL. + * + * @return string + */ + public function getPageContent($url) + { + return file_get_contents($url); + } + +} diff --git a/library/aik099/PHPUnit/Session/AbstractSessionStrategy.php b/library/aik099/PHPUnit/Session/AbstractSessionStrategy.php new file mode 100644 index 0000000..09f30f6 --- /dev/null +++ b/library/aik099/PHPUnit/Session/AbstractSessionStrategy.php @@ -0,0 +1,74 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserTestCase; +use Behat\Mink\Session; + +abstract class AbstractSessionStrategy implements ISessionStrategy +{ + + /** + * Determines if the session was just started. + * + * @var boolean|null + */ + protected $isFreshSession; + + /** + * @inheritDoc + */ + public function isFreshSession() + { + return $this->isFreshSession; + } + + /** + * @inheritDoc + */ + public function onTestEnded(BrowserTestCase $test_case) + { + + } + + /** + * @inheritDoc + */ + public function onTestFailed(BrowserTestCase $test_case, $exception) + { + + } + + /** + * @inheritDoc + */ + public function onTestSuiteEnded(BrowserTestCase $test_case) + { + + } + + /** + * Stops the session. + * + * @param Session|null $session Session. + * + * @return void + */ + protected function stopSession(Session $session = null) + { + if ( $session !== null && $session->isStarted() ) { + $session->stop(); + $this->isFreshSession = null; + } + } + +} diff --git a/library/aik099/PHPUnit/Session/ISessionStrategy.php b/library/aik099/PHPUnit/Session/ISessionStrategy.php new file mode 100644 index 0000000..3f3b159 --- /dev/null +++ b/library/aik099/PHPUnit/Session/ISessionStrategy.php @@ -0,0 +1,73 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use Behat\Mink\Session; + +/** + * Specifies how to create Session objects for running tests. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +interface ISessionStrategy +{ + + /** + * Returns Mink session with given browser configuration. + * + * @param BrowserConfiguration $browser Browser configuration for a session. + * + * @return Session + */ + public function session(BrowserConfiguration $browser); + + /** + * Determines if the session was just started. + * + * @return boolean|null + */ + public function isFreshSession(); + + /** + * Hook, called from "BrowserTestCase::tearDownTest" method. + * + * @param BrowserTestCase $test_case Test case. + * + * @return void + * @internal + */ + public function onTestEnded(BrowserTestCase $test_case); + + /** + * Hook, called from "BrowserTestCase::onNotSuccessfulTestCompat" method. + * + * @param BrowserTestCase $test_case Test case. + * @param \Exception|\Throwable $exception Exception. + * + * @return void + * @internal + */ + public function onTestFailed(BrowserTestCase $test_case, $exception); + + /** + * Hook, called from "BrowserTestCase::onTestSuiteEnded" method. + * + * @param BrowserTestCase $test_case Test case. + * + * @return void + * @internal + */ + public function onTestSuiteEnded(BrowserTestCase $test_case); + +} diff --git a/library/aik099/PHPUnit/Session/ISessionStrategyFactory.php b/library/aik099/PHPUnit/Session/ISessionStrategyFactory.php new file mode 100644 index 0000000..b3fb4cf --- /dev/null +++ b/library/aik099/PHPUnit/Session/ISessionStrategyFactory.php @@ -0,0 +1,41 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +/** + * Specifies how to create Session objects for running tests. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +interface ISessionStrategyFactory +{ + + /** + * Strategy, that create new session for each test in a test case. + */ + const TYPE_ISOLATED = 'isolated'; + + /** + * Strategy, that allows to share session across all tests in a single test case. + */ + const TYPE_SHARED = 'shared'; + + /** + * Creates specified session strategy. + * + * @param string $strategy_type Session strategy type. + * + * @return ISessionStrategy + */ + public function createStrategy($strategy_type); + +} diff --git a/library/aik099/PHPUnit/Session/IsolatedSessionStrategy.php b/library/aik099/PHPUnit/Session/IsolatedSessionStrategy.php new file mode 100644 index 0000000..7bbd7d2 --- /dev/null +++ b/library/aik099/PHPUnit/Session/IsolatedSessionStrategy.php @@ -0,0 +1,47 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use Behat\Mink\Session; + +/** + * Produces a new Session object shared for each test. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class IsolatedSessionStrategy extends AbstractSessionStrategy +{ + + /** + * @inheritDoc + */ + public function session(BrowserConfiguration $browser) + { + $session = new Session($browser->createDriver()); + $this->isFreshSession = true; + + return $session; + } + + /** + * @inheritDoc + */ + public function onTestEnded(BrowserTestCase $test_case) + { + $session = $test_case->getSession(false); + + $this->stopSession($session); + } + +} diff --git a/library/aik099/PHPUnit/Session/SessionStrategyFactory.php b/library/aik099/PHPUnit/Session/SessionStrategyFactory.php new file mode 100644 index 0000000..9905039 --- /dev/null +++ b/library/aik099/PHPUnit/Session/SessionStrategyFactory.php @@ -0,0 +1,66 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +/** + * Produces sessions. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class SessionStrategyFactory implements ISessionStrategyFactory +{ + + /** + * Session strategies. + * + * @var ISessionStrategy[] + */ + protected $sessionStrategies = array(); + + /** + * Registers a browser configuration. + * + * @param string $strategy_type Session strategy type. + * @param ISessionStrategy $session_strategy Session strategy. + * + * @return void + * @throws \InvalidArgumentException When session strategy is already registered. + */ + public function register($strategy_type, ISessionStrategy $session_strategy) + { + if ( isset($this->sessionStrategies[$strategy_type]) ) { + throw new \InvalidArgumentException( + 'Session strategy with type "' . $strategy_type . '" is already registered' + ); + } + + $this->sessionStrategies[$strategy_type] = $session_strategy; + } + + /** + * Creates specified session strategy. + * + * @param string $strategy_type Session strategy type. + * + * @return ISessionStrategy + * @throws \InvalidArgumentException When session strategy type is invalid. + */ + public function createStrategy($strategy_type) + { + if ( !isset($this->sessionStrategies[$strategy_type]) ) { + throw new \InvalidArgumentException('Session strategy type "' . $strategy_type . '" not registered'); + } + + return clone $this->sessionStrategies[$strategy_type]; + } + +} diff --git a/library/aik099/PHPUnit/Session/SessionStrategyManager.php b/library/aik099/PHPUnit/Session/SessionStrategyManager.php new file mode 100644 index 0000000..a4fc77d --- /dev/null +++ b/library/aik099/PHPUnit/Session/SessionStrategyManager.php @@ -0,0 +1,107 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; + +/** + * Manages session strategies used across browser tests. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class SessionStrategyManager +{ + + /** + * Browser configuration used in last executed test. + * + * @var string + */ + protected $lastUsedSessionStrategyHash; + + /** + * Session strategy, that was last requested in the browser configuration. + * + * @var ISessionStrategy + */ + protected $lastUsedSessionStrategy; + + /** + * Session strategy, that will be used by default. + * + * @var ISessionStrategy + */ + protected $defaultSessionStrategy; + + /** + * Session strategy factory. + * + * @var ISessionStrategyFactory + */ + private $_sessionStrategyFactory; + + /** + * Creates session strategy manager instance. + * + * @param ISessionStrategyFactory $session_strategy_factory Session strategy factory. + */ + public function __construct(ISessionStrategyFactory $session_strategy_factory) + { + $this->_sessionStrategyFactory = $session_strategy_factory; + } + + /** + * Creates default session strategy. + * + * @return ISessionStrategy + */ + public function getDefaultSessionStrategy() + { + if ( !$this->defaultSessionStrategy ) { + $this->defaultSessionStrategy = $this->_sessionStrategyFactory->createStrategy( + ISessionStrategyFactory::TYPE_ISOLATED + ); + } + + return $this->defaultSessionStrategy; + } + + /** + * Initializes session strategy using given browser test case. + * + * @param BrowserConfiguration $browser Browser configuration. + * @param BrowserTestCase $test_case Test case. + * + * @return ISessionStrategy + */ + public function getSessionStrategy(BrowserConfiguration $browser, BrowserTestCase $test_case) + { + /* + * This logic creates separate strategy for: + * - each browser configuration in BrowserTestCase::$browsers (for isolated strategy) + * - each browser configuration in BrowserTestCase::$browsers for each test case class (for shared strategy) + */ + $strategy_type = $browser->getSessionStrategy(); + $strategy_hash = $browser->getSessionStrategyHash($test_case); + + if ( $strategy_hash === $this->lastUsedSessionStrategyHash ) { + return $this->lastUsedSessionStrategy; + } + + $this->lastUsedSessionStrategy = $this->_sessionStrategyFactory->createStrategy($strategy_type); + $this->lastUsedSessionStrategyHash = $strategy_hash; + + return $this->lastUsedSessionStrategy; + } + +} diff --git a/library/aik099/PHPUnit/Session/SharedSessionStrategy.php b/library/aik099/PHPUnit/Session/SharedSessionStrategy.php new file mode 100644 index 0000000..0fccc57 --- /dev/null +++ b/library/aik099/PHPUnit/Session/SharedSessionStrategy.php @@ -0,0 +1,138 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use Behat\Mink\Session; +use ConsoleHelpers\PHPUnitCompat\Framework\IncompleteTestError; +use ConsoleHelpers\PHPUnitCompat\Framework\SkippedTestError; + +/** + * Keeps a Session object shared between test runs to save time. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class SharedSessionStrategy extends AbstractSessionStrategy +{ + + /** + * Original session strategy. + * + * @var ISessionStrategy + */ + private $_originalStrategy; + + /** + * Reference to created session. + * + * @var Session + */ + private $_session; + + /** + * Remembers if last test failed. + * + * @var boolean + */ + private $_lastTestFailed = false; + + /** + * Remembers original session strategy upon shared strategy creation. + * + * @param ISessionStrategy $original_strategy Original session strategy. + */ + public function __construct(ISessionStrategy $original_strategy) + { + $this->_originalStrategy = $original_strategy; + } + + /** + * @inheritDoc + */ + public function session(BrowserConfiguration $browser) + { + if ( $this->_lastTestFailed ) { + $this->stopAndForgetSession(); + $this->_lastTestFailed = false; + } + + if ( $this->_session === null ) { + $this->_session = $this->_originalStrategy->session($browser); + $this->isFreshSession = $this->_originalStrategy->isFreshSession(); + } + else { + $this->isFreshSession = false; + $this->_switchToMainWindow(); + } + + return $this->_session; + } + + /** + * Stops and forgets a session. + * + * @return void + */ + protected function stopAndForgetSession() + { + if ( $this->_session !== null ) { + $this->stopSession($this->_session); + $this->_session = null; + } + } + + /** + * Switches to window, that was created upon session creation. + * + * @return void + */ + private function _switchToMainWindow() + { + $this->_session->switchToWindow(); // Switch to the window before attempting to execute any window-based calls. + $actual_initial_window_name = $this->_session->getWindowName(); // Account for initial window rename. + + foreach ( $this->_session->getWindowNames() as $name ) { + if ( $name === $actual_initial_window_name ) { + continue; + } + + $this->_session->switchToWindow($name); + $this->_session->executeScript('window.close();'); + } + + $this->_session->switchToWindow(); + } + + /** + * @inheritDoc + */ + public function onTestFailed(BrowserTestCase $test_case, $exception) + { + if ( $exception instanceof IncompleteTestError || $exception instanceof SkippedTestError ) { + return; + } + + $this->_lastTestFailed = true; + } + + /** + * @inheritDoc + */ + public function onTestSuiteEnded(BrowserTestCase $test_case) + { + $session = $test_case->getSession(false); + + $this->stopSession($session); + } + +} diff --git a/library/aik099/PHPUnit/SessionStrategy/ISessionStrategy.php b/library/aik099/PHPUnit/SessionStrategy/ISessionStrategy.php deleted file mode 100644 index 9b51cbe..0000000 --- a/library/aik099/PHPUnit/SessionStrategy/ISessionStrategy.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use Behat\Mink\Session; - -/** - * Specifies how to create Session objects for running tests. - * - * @method \Mockery\Expectation shouldReceive - */ -interface ISessionStrategy -{ - - /** - * Returns Mink session with given browser configuration. - * - * @param BrowserConfiguration $browser Browser configuration for a session. - * - * @return Session - */ - public function session(BrowserConfiguration $browser); - - /** - * Called, when test fails. - * - * @param \Exception $e Exception. - * - * @return self - */ - public function notSuccessfulTest(\Exception $e); - - /** - * Called, when test ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTest(Session $session = null); - - /** - * Called, when test case ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTestCase(Session $session = null); - -} diff --git a/library/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategy.php b/library/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategy.php deleted file mode 100644 index 2177fb6..0000000 --- a/library/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategy.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use Behat\Mink\Session; - -/** - * Produces a new Session object shared for each test. - * - * @method \Mockery\Expectation shouldReceive - */ -class IsolatedSessionStrategy implements ISessionStrategy -{ - - /** - * Returns Mink session with given browser configuration. - * - * @param BrowserConfiguration $browser Browser configuration for a session. - * - * @return Session - */ - public function session(BrowserConfiguration $browser) - { - $session = $browser->createSession(); - $session->start(); - - return $session; - } - - /** - * Called, when test fails. - * - * @param \Exception $e Exception. - * - * @return self - */ - public function notSuccessfulTest(\Exception $e) - { - return $this; - } - - /** - * Called, when test ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTest(Session $session = null) - { - if ( $session !== null ) { - $session->stop(); - } - - return $this; - } - - /** - * Called, when test case ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTestCase(Session $session = null) - { - return $this; - } - -} diff --git a/library/aik099/PHPUnit/SessionStrategy/SessionStrategyManager.php b/library/aik099/PHPUnit/SessionStrategy/SessionStrategyManager.php deleted file mode 100644 index bbc42ae..0000000 --- a/library/aik099/PHPUnit/SessionStrategy/SessionStrategyManager.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit\SessionStrategy; - - -/** - * Manages session strategies used across browser tests. - * - * @method \Mockery\Expectation shouldReceive - */ -class SessionStrategyManager -{ - - /** - * Strategy, that create new session for each test in a test case. - */ - const ISOLATED_STRATEGY = 'isolated'; - - /** - * Strategy, that allows to share session across all tests in a single test case. - */ - const SHARED_STRATEGY = 'shared'; - - /** - * Browser configuration used in last executed test. - * - * @var string - */ - protected $lastUsedSessionStrategyHash; - - /** - * Session strategy, that was requested in browser configuration. - * - * @var ISessionStrategy[] - */ - protected $sessionStrategiesInUse = array(); - - /** - * Session strategy, that will be used by default. - * - * @var ISessionStrategy - */ - protected $defaultSessionStrategy; - - /** - * No direct instantiation. - */ - protected function __construct() - { - - } - - /** - * Returns instance of strategy manager. - * - * @return self - */ - public static function getInstance() - { - static $instance = null; - - if ( null === $instance ) { - $instance = new static(); - } - - return $instance; - } - - /** - * Initializes session strategy using given browser test case. - * - * @param string $session_strategy Session strategy. - * @param string $session_strategy_hash Session strategy hash. - * - * @return ISessionStrategy - */ - public function getSessionStrategy($session_strategy, $session_strategy_hash) - { - // This logic creates separate strategy for: - // - each browser configuration in BrowserTestCase::$browsers (for isolated strategy) - // - each browser configuration in BrowserTestCase::$browsers for each test case class (for shared strategy) - - if ( $session_strategy_hash != $this->lastUsedSessionStrategyHash ) { - switch ( $session_strategy ) { - case self::ISOLATED_STRATEGY: - $this->sessionStrategiesInUse[$session_strategy_hash] = $this->createSessionStrategy(false); - break; - - case self::SHARED_STRATEGY: - $this->sessionStrategiesInUse[$session_strategy_hash] = $this->createSessionStrategy(true); - break; - } - } - - $this->lastUsedSessionStrategyHash = $session_strategy_hash; - - return $this->sessionStrategiesInUse[$session_strategy_hash]; - } - - /** - * Creates specified session strategy. - * - * @param boolean $share_session Share or not the session. - * - * @return ISessionStrategy - * @throws \InvalidArgumentException When incorrect argument is given. - */ - public function createSessionStrategy($share_session) - { - if ( !is_bool($share_session) ) { - throw new \InvalidArgumentException('The shared session support can only be switched on or off.'); - } - - if ( $share_session ) { - return new SharedSessionStrategy(new IsolatedSessionStrategy()); - } - - return new IsolatedSessionStrategy(); - } - - /** - * Creates default session strategy. - * - * @return ISessionStrategy - */ - public function getDefaultSessionStrategy() - { - if ( !$this->defaultSessionStrategy ) { - $this->defaultSessionStrategy = $this->createSessionStrategy(false); - } - - return $this->defaultSessionStrategy; - } - -} diff --git a/library/aik099/PHPUnit/SessionStrategy/SharedSessionStrategy.php b/library/aik099/PHPUnit/SessionStrategy/SharedSessionStrategy.php deleted file mode 100644 index 2a58c57..0000000 --- a/library/aik099/PHPUnit/SessionStrategy/SharedSessionStrategy.php +++ /dev/null @@ -1,172 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use Behat\Mink\Driver\Selenium2Driver; -use Behat\Mink\Session; - -/** - * Keeps a Session object shared between test runs to save time. - * - * @method \Mockery\Expectation shouldReceive - */ -class SharedSessionStrategy implements ISessionStrategy -{ - - /** - * Original session strategy. - * - * @var ISessionStrategy - */ - private $_originalStrategy; - - /** - * Reference to created session. - * - * @var Session - */ - private $_session; - - /** - * Window name, which was opened upon session creation. - * - * @var string - */ - private $_mainWindow; - - /** - * Remembers if last test failed. - * - * @var boolean - */ - private $_lastTestWasNotSuccessful = false; - - /** - * Remembers original session strategy upon shared strategy creation. - * - * @param ISessionStrategy $original_strategy Original session strategy. - */ - public function __construct(ISessionStrategy $original_strategy) - { - $this->_originalStrategy = $original_strategy; - } - - /** - * Returns Mink session with given browser configuration. - * - * @param BrowserConfiguration $browser Browser configuration for a session. - * - * @return Session - */ - public function session(BrowserConfiguration $browser) - { - if ( $this->_lastTestWasNotSuccessful ) { - if ( $this->_session !== null ) { - $this->_session->stop(); - $this->_session = null; - } - - $this->_lastTestWasNotSuccessful = false; - } - - if ( $this->_session === null ) { - $this->_session = $this->_originalStrategy->session($browser); - $this->rememberMainWindow(); - } - else { - // if session is reused, then switch to window, that was created along with session creation - $this->restoreMainWindow(); - } - - return $this->_session; - } - - /** - * Remember window name, which was created along with session. - * - * @return self - */ - protected function rememberMainWindow() - { - $driver = $this->_session->getDriver(); - - if ( $driver instanceof Selenium2Driver ) { - $wd_session = $driver->getWebDriverSession(); - $this->_mainWindow = $wd_session->window_handle(); - } - - return $this; - } - - /** - * Switches to window, that was created upon session creation. - * - * @return self - */ - protected function restoreMainWindow() - { - $this->_session->switchToWindow($this->_mainWindow); - - return $this; - } - - /** - * Called, when test fails. - * - * @param \Exception $e Exception. - * - * @return self - */ - public function notSuccessfulTest(\Exception $e) - { - if ( $e instanceof \PHPUnit_Framework_IncompleteTestError ) { - return $this; - } - elseif ( $e instanceof \PHPUnit_Framework_SkippedTestError ) { - return $this; - } - - $this->_lastTestWasNotSuccessful = true; - - return $this; - } - - /** - * Called, when test ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTest(Session $session = null) - { - return $this; - } - - /** - * Called, when test case ends. - * - * @param Session|null $session Session. - * - * @return self - */ - public function endOfTestCase(Session $session = null) - { - if ( $session !== null ) { - $session->stop(); - } - - return $this; - } - -} diff --git a/library/aik099/PHPUnit/TestSuite.php b/library/aik099/PHPUnit/TestSuite.php deleted file mode 100644 index fbc2eae..0000000 --- a/library/aik099/PHPUnit/TestSuite.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit; - - -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; - - -/** - * Test Suite class for browser tests. - */ -class TestSuite extends TestSuiteBase -{ - - /** - * Creating TestSuite from given class. - * - * @param string $class_name Descendant of TestCase class. - * - * @return self - */ - public static function fromTestCaseClass($class_name) - { - $suite = new static(); - $suite->setName($class_name); - - $class = new \ReflectionClass($class_name); - $static_properties = $class->getStaticProperties(); - - $session_strategy_manager = SessionStrategyManager::getInstance(); - - // create tests from test methods for multiple browsers - if ( !empty($static_properties['browsers']) ) { - foreach ($static_properties['browsers'] as $browser) { - $suite->addTest(static::createBrowserSuite($class, $browser, $session_strategy_manager)); - } - } - else { - // create tests from test methods for single browser - $suite->addTestMethods($class, $session_strategy_manager); - } - - return $suite; - } - - /** - * Creates browser suite. - * - * @param \ReflectionClass $class Class. - * @param array $browser Browser configuration. - * @param SessionStrategyManager $session_strategy_manager Session strategy manager. - * - * @return BrowserSuite - */ - protected static function createBrowserSuite(\ReflectionClass $class, array $browser, SessionStrategyManager $session_strategy_manager) - { - $suite = BrowserSuite::fromClassAndBrowser($class->name, $browser); - - $suite->addTestMethods($class, $session_strategy_manager); - $suite->setBrowserFromConfiguration($browser); - - return $suite; - } - -} diff --git a/library/aik099/PHPUnit/TestSuite/AbstractTestSuite.php b/library/aik099/PHPUnit/TestSuite/AbstractTestSuite.php new file mode 100644 index 0000000..39b3c09 --- /dev/null +++ b/library/aik099/PHPUnit/TestSuite/AbstractTestSuite.php @@ -0,0 +1,178 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\Session\SessionStrategyManager; +use ConsoleHelpers\PHPUnitCompat\AbstractTestSuite as PHPUnitCompatAbstractTestSuite; +use ConsoleHelpers\PHPUnitCompat\Framework\DataProviderTestSuite; +use PHPUnit\Util\Test as TestUtil; + +/** + * Base Test Suite class for browser tests. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +abstract class AbstractTestSuite extends PHPUnitCompatAbstractTestSuite +{ + + /** + * Overriding the default: Selenium suites are always built from a TestCase class. + * + * @var boolean + */ + protected $testCase = true; + + /** + * Adds test methods to the suite. + * + * @param string $class_name Test case class name. + * + * @return self + */ + public function addTestMethods($class_name) + { + $class = new \ReflectionClass($class_name); + + if ( \method_exists($this, 'isTestMethod') ) { + // PHPUnit < 8.0 is calling "isTestMethod" inside "TestSuite::addTestMethod". + foreach ( $this->getTestMethods($class) as $method ) { + $this->addTestMethod($class, $method); + } + } + else { + // PHPUnit >= 8.0 is calling "TestUtil::isTestMethod" outside of "TestSuite::addTestMethod". + foreach ( $this->getTestMethods($class) as $method ) { + if ( TestUtil::isTestMethod($method) ) { + $this->addTestMethod($class, $method); + } + } + } + + return $this; + } + + /** + * Returns test methods. + * + * @param \ReflectionClass $class Reflection class. + * + * @return \ReflectionMethod[] + */ + protected function getTestMethods(\ReflectionClass $class) + { + $ret = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + + return \array_filter($ret, function (\ReflectionMethod $method) { + return !$method->isStatic(); + }); + } + + /** + * Sets session strategy manager recursively to all tests. + * + * @param SessionStrategyManager $session_strategy_manager Session strategy manager. + * @param IBrowserConfigurationFactory $browser_configuration_factory Browser configuration factory. + * @param RemoteCoverageHelper $remote_coverage_helper Remote coverage helper. + * @param array $tests Tests to process. + * + * @return self + */ + public function setTestDependencies( + SessionStrategyManager $session_strategy_manager, + IBrowserConfigurationFactory $browser_configuration_factory, + RemoteCoverageHelper $remote_coverage_helper, + array $tests = null + ) { + if ( !isset($tests) ) { + $tests = $this->tests(); + } + + foreach ( $tests as $test ) { + if ( $test instanceof DataProviderTestSuite ) { + $this->setTestDependencies( + $session_strategy_manager, + $browser_configuration_factory, + $remote_coverage_helper, + $test->tests() + ); + } + else { + /** @var BrowserTestCase $test */ + $test->setSessionStrategyManager($session_strategy_manager); + $test->setBrowserConfigurationFactory($browser_configuration_factory); + $test->setRemoteCoverageHelper($remote_coverage_helper); + } + } + + return $this; + } + + /** + * @inheritDoc + */ + public function runCompat($result = null) + { + $result = parent::runCompat($result); + + $this->triggerTestSuiteEnded(); + + return $result; + } + + /** + * Report back suite ending to each it's test. + * + * @param \IteratorAggregate|null $test_suite Test suite. + * + * @return void + */ + protected function triggerTestSuiteEnded(\IteratorAggregate $test_suite = null) + { + if ( $test_suite === null ) { + $test_suite = $this; + } + + foreach ( $test_suite as $test ) { + if ( $test instanceof DataProviderTestSuite ) { + /* + * Use our test suite method to tear down + * supported test suites wrapped in a data + * provider test suite. + */ + $this->triggerTestSuiteEnded($test); + } + else { + /* + * Once browser test suite ends the shared sessions strategy can stop the browser. + */ + /** @var BrowserTestCase|AbstractTestSuite $test */ + $test->onTestSuiteEnded(); + } + } + } + + /** + * Indicates end of the test suite. + * + * @return void + * + * @codeCoverageIgnore + */ + public function onTestSuiteEnded() + { + // Method created just to simplify tearDown method. + } + +} diff --git a/library/aik099/PHPUnit/TestSuite/BrowserTestSuite.php b/library/aik099/PHPUnit/TestSuite/BrowserTestSuite.php new file mode 100644 index 0000000..b12034a --- /dev/null +++ b/library/aik099/PHPUnit/TestSuite/BrowserTestSuite.php @@ -0,0 +1,70 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\BrowserTestCase; +use ConsoleHelpers\PHPUnitCompat\Framework\DataProviderTestSuite; + +/** + * Test Suite class for a set of tests from a single Test Case Class executed with a particular browser. + */ +class BrowserTestSuite extends AbstractTestSuite +{ + + /** + * Generates suite name by the browser configuration. + * + * @param array $browser Browser configuration. + * + * @return string + */ + public function nameFromBrowser(array $browser) + { + $try_settings = array('alias', 'browserName', 'name'); + + foreach ( $try_settings as $try_setting ) { + if ( isset($browser[$try_setting]) ) { + return $browser[$try_setting]; + } + } + + return 'undefined'; + } + + /** + * Sets given browser to be used in each underlying test cases and test suites. + * + * @param array $browser Browser configuration. + * @param array $tests Tests to process. + * + * @return self + */ + public function setBrowserFromConfiguration(array $browser, array $tests = null) + { + if ( !isset($tests) ) { + $tests = $this->tests(); + } + + foreach ( $tests as $test ) { + if ( $test instanceof DataProviderTestSuite ) { + $this->setBrowserFromConfiguration($browser, $test->tests()); + } + else { + /** @var BrowserTestCase $test */ + $test->setBrowserFromConfiguration($browser); + } + } + + return $this; + } + +} diff --git a/library/aik099/PHPUnit/TestSuite/RegularTestSuite.php b/library/aik099/PHPUnit/TestSuite/RegularTestSuite.php new file mode 100644 index 0000000..cfdd41c --- /dev/null +++ b/library/aik099/PHPUnit/TestSuite/RegularTestSuite.php @@ -0,0 +1,21 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\TestSuite; + + +/** + * Test Suite class for browser tests. + */ +class RegularTestSuite extends AbstractTestSuite +{ + + +} diff --git a/library/aik099/PHPUnit/TestSuite/TestSuiteFactory.php b/library/aik099/PHPUnit/TestSuite/TestSuiteFactory.php new file mode 100644 index 0000000..8baf702 --- /dev/null +++ b/library/aik099/PHPUnit/TestSuite/TestSuiteFactory.php @@ -0,0 +1,159 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\Application; +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\IApplicationAware; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\Session\SessionStrategyManager; + +/** + * Creates test suites based on test case class configuration. + * + * @method \Mockery\Expectation shouldReceive(string $name) + */ +class TestSuiteFactory implements IApplicationAware +{ + + /** + * Session strategy manager. + * + * @var SessionStrategyManager + */ + private $_sessionStrategyManager; + + /** + * Application. + * + * @var Application + */ + protected $application; + + /** + * Browser configuration factory. + * + * @var IBrowserConfigurationFactory + */ + private $_browserConfigurationFactory; + + /** + * Remote coverage helper. + * + * @var RemoteCoverageHelper + */ + private $_remoteCoverageHelper; + + /** + * Creates test suite builder instance. + * + * @param SessionStrategyManager $session_strategy_manager Session strategy manager. + * @param IBrowserConfigurationFactory $browser_configuration_factory Browser configuration factory. + * @param RemoteCoverageHelper $remote_coverage_helper Remote coverage helper. + */ + public function __construct( + SessionStrategyManager $session_strategy_manager, + IBrowserConfigurationFactory $browser_configuration_factory, + RemoteCoverageHelper $remote_coverage_helper + ) { + $this->_sessionStrategyManager = $session_strategy_manager; + $this->_browserConfigurationFactory = $browser_configuration_factory; + $this->_remoteCoverageHelper = $remote_coverage_helper; + } + + /** + * Sets application. + * + * @param Application $application The application. + * + * @return void + */ + public function setApplication(Application $application) + { + $this->application = $application; + } + + /** + * Creates test suite based on given test case class. + * + * @param string $class_name Test case class name. + * + * @return AbstractTestSuite + */ + public function createSuiteFromTestCase($class_name) + { + /** @var RegularTestSuite $suite */ + $suite = $this->application->getObject('regular_test_suite'); + $suite->setName($class_name); + + $browsers = $this->_getBrowsers($class_name); + + if ( $browsers ) { + // Create tests from test methods for multiple browsers. + foreach ( $browsers as $browser ) { + $suite->addTest($this->_createBrowserSuite($class_name, $browser)); + } + } + else { + // Create tests from test methods for single browser. + $suite->addTestMethods($class_name); + $suite->setTestDependencies( + $this->_sessionStrategyManager, + $this->_browserConfigurationFactory, + $this->_remoteCoverageHelper + ); + } + + return $suite; + } + + /** + * Returns browser configuration of a class. + * + * @param string $class_name Test case class name. + * + * @return array + */ + private function _getBrowsers($class_name) + { + $class = new \ReflectionClass($class_name); + $static_properties = $class->getStaticProperties(); + + return !empty($static_properties['browsers']) ? $static_properties['browsers'] : array(); + } + + /** + * Creates browser suite. + * + * @param string $class_name Descendant of TestCase class. + * @param array $browser Browser configuration. + * + * @return BrowserTestSuite + */ + private function _createBrowserSuite($class_name, array $browser) + { + /** @var BrowserTestSuite $suite */ + $suite = $this->application->getObject('browser_test_suite'); + $suite->setName($class_name . ': ' . $suite->nameFromBrowser($browser)); + + $suite->addTestMethods($class_name); + $suite->setTestDependencies( + $this->_sessionStrategyManager, + $this->_browserConfigurationFactory, + $this->_remoteCoverageHelper + ); + $suite->setBrowserFromConfiguration($browser); + + return $suite; + } + +} diff --git a/library/aik099/PHPUnit/TestSuiteBase.php b/library/aik099/PHPUnit/TestSuiteBase.php deleted file mode 100644 index 22d08d9..0000000 --- a/library/aik099/PHPUnit/TestSuiteBase.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace aik099\PHPUnit; - - -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; - - -/** - * Base Test Suite class for browser tests. - * - * @method \Mockery\Expectation shouldReceive - */ -abstract class TestSuiteBase extends \PHPUnit_Framework_TestSuite -{ - - /** - * Overriding the default: Selenium suites are always built from a TestCase class. - * - * @var boolean - */ - protected $testCase = true; - - /** - * Adds test methods to the suite. - * - * @param \ReflectionClass $class Class reflection. - * @param SessionStrategyManager $session_strategy_manager Session strategy manager. - * - * @return self - */ - public function addTestMethods(\ReflectionClass $class, SessionStrategyManager $session_strategy_manager) - { - foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - $this->addTestMethod($class, $method); - } - - $this->setSessionStrategyManager($session_strategy_manager); - - return $this; - } - - /** - * Sets session strategy manager. - * - * @param SessionStrategyManager $session_strategy_manager Session strategy manager. - * - * @return self - */ - public function setSessionStrategyManager(SessionStrategyManager $session_strategy_manager) - { - /* @var $test \aik099\PHPUnit\BrowserTestCase */ - foreach ( $this->tests() as $test ) { - $test->setSessionStrategyManager($session_strategy_manager); - } - - return $this; - } - - /** - * Report back suite ending to each it's test. - * - * @return void - */ - protected function tearDown() - { - /* @var $test BrowserTestCase */ - - foreach ( $this->tests() as $test ) { - $test->endOfTestCase(); - } - } - - /** - * Indicates end of the test suite. - * - * @return void - * @codeCoverageIgnore - */ - public function endOfTestCase() - { - // method created just to simplify tearDown method - } - -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a10bbf7..65120c7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,19 @@ - + verbose="true" + beStrictAboutChangesToGlobalState="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTestSize="true"> tests/aik099/PHPUnit + + tests/PimpleCopy/Pimple + - - - + + + + - library/aik099 + library/aik099/PHPUnit + library/PimpleCopy/Pimple diff --git a/tests/PimpleCopy/Pimple/Fixtures/Invokable.php b/tests/PimpleCopy/Pimple/Fixtures/Invokable.php new file mode 100644 index 0000000..d5081b0 --- /dev/null +++ b/tests/PimpleCopy/Pimple/Fixtures/Invokable.php @@ -0,0 +1,41 @@ +value = $value; + + return $service; + } + +} diff --git a/tests/PimpleCopy/Pimple/Fixtures/NonInvokable.php b/tests/PimpleCopy/Pimple/Fixtures/NonInvokable.php new file mode 100644 index 0000000..4aefd06 --- /dev/null +++ b/tests/PimpleCopy/Pimple/Fixtures/NonInvokable.php @@ -0,0 +1,37 @@ +factory(function () { + return new Service(); + }); + } + +} diff --git a/tests/PimpleCopy/Pimple/Fixtures/Service.php b/tests/PimpleCopy/Pimple/Fixtures/Service.php new file mode 100644 index 0000000..9147dd8 --- /dev/null +++ b/tests/PimpleCopy/Pimple/Fixtures/Service.php @@ -0,0 +1,38 @@ + + */ +class Service +{ + + public $value; + +} diff --git a/tests/PimpleCopy/Pimple/PimpleServiceProviderInterfaceTest.php b/tests/PimpleCopy/Pimple/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 0000000..74d7aaa --- /dev/null +++ b/tests/PimpleCopy/Pimple/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,81 @@ + + */ +class PimpleServiceProviderInterfaceTest extends TestCase +{ + + public function testProvider() + { + $pimple = new Container(); + + $pimple_service_provider = new Fixtures\PimpleServiceProvider(); + $pimple_service_provider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf(Service::class, $pimple['service']); + + $service_one = $pimple['factory']; + $this->assertInstanceOf(Service::class, $service_one); + + $service_two = $pimple['factory']; + $this->assertInstanceOf(Service::class, $service_two); + + $this->assertNotSame($service_one, $service_two); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), array( + 'anotherParameter' => 'anotherValue', + )); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf(Service::class, $pimple['service']); + + $service_one = $pimple['factory']; + $this->assertInstanceOf(Service::class, $service_one); + + $service_two = $pimple['factory']; + $this->assertInstanceOf(Service::class, $service_two); + + $this->assertNotSame($service_one, $service_two); + } + +} diff --git a/tests/PimpleCopy/Pimple/PimpleTest.php b/tests/PimpleCopy/Pimple/PimpleTest.php new file mode 100644 index 0000000..1abd5ff --- /dev/null +++ b/tests/PimpleCopy/Pimple/PimpleTest.php @@ -0,0 +1,460 @@ + + */ +class PimpleTest extends TestCase +{ + use ExpectException; + + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf(Service::class, $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $service_one = $pimple['service']; + $this->assertInstanceOf(Service::class, $service_one); + + $service_two = $pimple['service']; + $this->assertInstanceOf(Service::class, $service_two); + + $this->assertNotSame($service_one, $service_two); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = array('param' => 'value'); + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + public function testOffsetGetValidatesKeyIsPresent() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $service_one = $pimple['shared_service']; + $this->assertInstanceOf(Service::class, $service_one); + + $service_two = $pimple['shared_service']; + $this->assertInstanceOf(Service::class, $service_two); + + $this->assertSame($service_one, $service_two); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { + return 'foo'; + }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $serviceProviderMock = m::mock(ServiceProviderInterface::class); + $serviceProviderMock->shouldReceive('register'); + + $this->assertSame($pimple, $pimple->register($serviceProviderMock)); + } + + public function testRawValidatesKeyIsPresent() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $service_one = $pimple['shared_service']; + $this->assertInstanceOf(Service::class, $service_one); + $service_two = $pimple['shared_service']; + $this->assertInstanceOf(Service::class, $service_two); + $this->assertSame($service_one, $service_two); + $this->assertSame($service_one->value, $service_two->value); + + $pimple->extend('factory_service', $service); + $service_one = $pimple['factory_service']; + $this->assertInstanceOf(Service::class, $service_one); + $service_two = $pimple['factory_service']; + $this->assertInstanceOf(Service::class, $service_two); + $this->assertNotSame($service_one, $service_two); + $this->assertNotSame($service_one->value, $service_two->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if ( extension_loaded('pimple') ) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { + return; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { + return; + }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + public function testExtendValidatesKeyIsPresent() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf(Service::class, $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf(NonInvokable::class, $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Callable is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.'); + + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * Provider for invalid service definitions. + */ + public static function badServiceDefinitionProvider() + { + return array( + array(123), + array(new Fixtures\NonInvokable()), + ); + } + + /** + * Provider for service definitions. + */ + public static function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + },), + array(new Fixtures\Invokable()), + ); + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + public function testOverridingServiceAfterFreeze() + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('Cannot override frozen service "foo".'); + + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return $foo . '.bar'; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return $foo . '.baz'; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return $bar . '.baz'; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } + +} diff --git a/tests/aik099/PHPUnit/AbstractTestCase.php b/tests/aik099/PHPUnit/AbstractTestCase.php new file mode 100644 index 0000000..f959e78 --- /dev/null +++ b/tests/aik099/PHPUnit/AbstractTestCase.php @@ -0,0 +1,21 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit; + + +use PHPUnit\Framework\TestCase; + +abstract class AbstractTestCase extends TestCase +{ + + use TVerifyTestExpectations; + +} diff --git a/tests/aik099/PHPUnit/ApplicationTest.php b/tests/aik099/PHPUnit/ApplicationTest.php new file mode 100644 index 0000000..4c8f480 --- /dev/null +++ b/tests/aik099/PHPUnit/ApplicationTest.php @@ -0,0 +1,97 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit; + + +use aik099\PHPUnit\Application; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\TestSuite\TestSuiteFactory; + +class ApplicationTest extends AbstractTestCase +{ + + use ExpectException; + + /** + * Application. + * + * @var Application + */ + private $_application; + + /** + * @before + */ + protected function setUpTest() + { + $this->_application = new Application(); + } + + /** + * Test description. + * + * @return void + */ + public function testInstanceIsShared() + { + $this->assertSame(Application::getInstance(), Application::getInstance()); + } + + /** + * Test description. + * + * @return void + */ + public function testGetTestSuiteFactory() + { + $this->assertInstanceOf(TestSuiteFactory::class, $this->_application->getTestSuiteFactory()); + } + + /** + * Test description. + * + * @return void + */ + public function testGetObject() + { + $this->assertInstanceOf(Application::class, $this->_application->getObject('application')); + } + + /** + * Test description. + * + * @return void + */ + public function testReplaceObjectSuccess() + { + $object = new \stdClass(); + $this->_application->replaceObject('application', function () use ($object) { + return $object; + }); + + $this->assertSame($object, $this->_application->getObject('application')); + } + + /** + * Test description. + * + * @return void + */ + public function testReplaceObjectFailure() + { + $this->expectException('InvalidArgumentException'); + + $this->_application->replaceObject('bad_service', function () { + + }); + } + +} diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/APIClientFactoryTest.php b/tests/aik099/PHPUnit/BrowserConfiguration/APIClientFactoryTest.php new file mode 100644 index 0000000..a409a85 --- /dev/null +++ b/tests/aik099/PHPUnit/BrowserConfiguration/APIClientFactoryTest.php @@ -0,0 +1,64 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\APIClient\BrowserStackAPIClient; +use aik099\PHPUnit\APIClient\SauceLabsAPIClient; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use Mockery as m; +use PHPUnit\Framework\TestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; + +class APIClientFactoryTest extends TestCase +{ + + use ExpectException; + + public function testBrowserStackAPIClient() + { + $browser = m::mock(BrowserStackBrowserConfiguration::class); + $browser->shouldReceive('getApiUsername')->once()->andReturn('username'); + $browser->shouldReceive('getApiKey')->once()->andReturn('key'); + + $factory = new APIClientFactory(); + + $this->assertInstanceOf(BrowserStackAPIClient::class, $factory->getAPIClient($browser)); + } + + public function testSauceLabsAPIClient() + { + $browser = m::mock(SauceLabsBrowserConfiguration::class); + $browser->shouldReceive('getApiUsername')->once()->andReturn('username'); + $browser->shouldReceive('getApiKey')->once()->andReturn('key'); + + $factory = new APIClientFactory(); + + $this->assertInstanceOf(SauceLabsAPIClient::class, $factory->getAPIClient($browser)); + } + + public function testUnknownAPIClient() + { + $browser = m::mock(BrowserConfiguration::class); + $browser->shouldReceive('getType')->once()->andReturn('browser config type'); + + $factory = new APIClientFactory(); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The "browser config type" browser configuration is not supported.'); + + $factory->getAPIClient($browser); + } + +} diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfigurationTestCase.php b/tests/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfigurationTestCase.php new file mode 100644 index 0000000..d3202d0 --- /dev/null +++ b/tests/aik099/PHPUnit/BrowserConfiguration/ApiBrowserConfigurationTestCase.php @@ -0,0 +1,482 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\APIClient\IAPIClient; +use aik099\PHPUnit\BrowserConfiguration\ApiBrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; +use Mockery as m; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use Behat\Mink\Driver\Selenium2Driver; +use Behat\Mink\Driver\DriverInterface; +use Behat\Mink\Session; + +abstract class ApiBrowserConfigurationTestCase extends BrowserConfigurationTest +{ + + use ExpectException; + + const PORT = 80; + + const AUTOMATIC_TEST_NAME = 'AUTOMATIC'; + + /** + * Desired capabilities use to configure the tunnel. + * + * @var array + */ + protected $tunnelCapabilities = array(); + + /** + * API client. + * + * @var IAPIClient + */ + protected $apiClient; + + /** + * Driver factory registry. + * + * @var APIClientFactory|m\MockInterface + */ + protected $apiClientFactory; + + /** + * @before + */ + protected function setUpTest() + { + if ( $this->needsAPIClient() ) { + $this->apiClient = m::mock(IAPIClient::class); + } + + $this->apiClientFactory = m::mock(APIClientFactory::class); + + parent::setUpTest(); + + if ( $this->needsAPIClient() ) { + $this->apiClientFactory->shouldReceive('getAPIClient') + ->with($this->browser) + ->once() + ->andReturn($this->apiClient); + } + + $this->setup['port'] = 80; + $this->setup['apiUsername'] = 'UN'; + $this->setup['apiKey'] = 'AK'; + } + + /** + * Test description. + * + * @return void + */ + public function testSetup() + { + parent::testSetup(); + + $this->assertSame($this->setup['apiUsername'], $this->browser->getApiUsername()); + $this->assertSame($this->setup['apiKey'], $this->browser->getApiKey()); + } + + public function testSnakeCaseParameters() + { + $this->browser->setup(array( + 'api_username' => 'old-user', + 'api_key' => 'old-key', + )); + + $this->assertEquals('old-user', $this->browser->getApiUsername()); + $this->assertEquals('old-key', $this->browser->getApiKey()); + + $this->browser->setup(array( + 'api_username' => 'old-user', + 'api_key' => 'old-key', + 'apiUsername' => 'new-user', + 'apiKey' => 'new-key', + )); + + $this->assertEquals('old-user', $this->browser->getApiUsername()); + $this->assertEquals('old-key', $this->browser->getApiKey()); + + $this->browser->setup(array( + 'apiUsername' => 'new-user', + 'apiKey' => 'new-key', + )); + + $this->assertEquals('new-user', $this->browser->getApiUsername()); + $this->assertEquals('new-key', $this->browser->getApiKey()); + } + + /** + * Test description. + * + * @return void + */ + public function testSetAPICorrect() + { + $browser = $this->createBrowserConfiguration(); + + $this->assertEmpty($browser->getApiUsername()); + $this->assertEmpty($browser->getApiKey()); + } + + /** + * Test description. + * + * @return void + */ + public function testSetHostCorrect() + { + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); + + $this->assertSame($browser, $browser->setHost('EXAMPLE_HOST')); + $this->assertSame('A:B@ondemand.saucelabs.com', $browser->getHost()); + } + + /** + * Test description. + * + * @return void + */ + public function testSetPortCorrect() + { + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); + + $this->assertSame($browser, $browser->setPort(5555)); + $this->assertSame(80, $browser->getPort()); + } + + /** + * Test description. + * + * @return void + */ + public function testSetBrowserNameCorrect() + { + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); + + $this->assertSame($browser, $browser->setBrowserName('')); + $this->assertSame('chrome', $browser->getBrowserName()); + } + + /** + * Test description. + * + * @param array|null $desired_capabilities Desired capabilities. + * @param array|null $expected Expected capabilities. + * + * @return void + * @dataProvider desiredCapabilitiesDataProvider + */ + public function testSetDesiredCapabilitiesCorrect(array $desired_capabilities = null, array $expected = null) + { + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); + + $this->assertSame($browser, $browser->setDesiredCapabilities($desired_capabilities)); + $this->assertSame($expected, $browser->getDesiredCapabilities(), 'Failed changing the desired capabilities.'); + } + + /** + * Desired capability data provider. + * + * @return array + * @throws \RuntimeException When not overridden in a subclass. + */ + public static function desiredCapabilitiesDataProvider() + { + throw new \RuntimeException('Not overridden in a subclass'); + } + + /** + * Test description. + * + * @param string $session_strategy Session strategy. + * @param string $test_name Expected job name. + * @param string $build_env_name Name of ENV variable to set build number to. + * @param string $build_number Build number. + * + * @return void + * @dataProvider setupProcessDataProvider + */ + public function testTestSetupProcess($session_strategy, $test_name, $build_env_name = null, $build_number = null) + { + // Reset any global env vars that might be left from previous tests. + $hhvm_hack = defined('HHVM_VERSION') ? '=' : ''; + putenv('BUILD_NUMBER' . $hhvm_hack); + putenv('TRAVIS_BUILD_NUMBER' . $hhvm_hack); + + if ( isset($build_number) ) { + putenv($build_env_name . '=' . $build_number); + } + + $this->browser->setSessionStrategy($session_strategy); + + $test_case = $this->createTestCase($test_name); + $test_case->shouldReceive('toString') + ->times($this->_isAutomaticTestName($test_name) ? 0 : 1) + ->andReturn($test_name); + + if ( $this->_isAutomaticTestName($test_name) ) { + $test_name = get_class($test_case); + } + + $this->browser->onTestSetup($test_case); + + $desired_capabilities = $this->browser->getDesiredCapabilities(); + + $this->assertArrayHasKey(ApiBrowserConfiguration::NAME_CAPABILITY, $desired_capabilities); + $this->assertEquals($test_name, $desired_capabilities[ApiBrowserConfiguration::NAME_CAPABILITY]); + + if ( isset($build_number) ) { + $this->assertArrayHasKey(ApiBrowserConfiguration::BUILD_NUMBER_CAPABILITY, $desired_capabilities); + $this->assertEquals($build_number, $desired_capabilities[ApiBrowserConfiguration::BUILD_NUMBER_CAPABILITY]); + } + else { + $this->assertArrayNotHasKey(ApiBrowserConfiguration::BUILD_NUMBER_CAPABILITY, $desired_capabilities); + } + } + + /** + * Checks that test name is automatic. + * + * @param string $test_name Expected job name. + * + * @return boolean + */ + private function _isAutomaticTestName($test_name) + { + return $test_name == self::AUTOMATIC_TEST_NAME; + } + + /** + * Data provider for setup process test. + * + * @return array + */ + public static function setupProcessDataProvider() + { + $seed = uniqid(); + + return array( + 'isolated, name, jenkins' => array( + ISessionStrategyFactory::TYPE_ISOLATED, 'TEST_NAME', 'BUILD_NUMBER', 'JENKINS ' . $seed, + ), + 'shared, no name, jenkins' => array( + ISessionStrategyFactory::TYPE_SHARED, self::AUTOMATIC_TEST_NAME, 'BUILD_NUMBER', 'JENKINS ' . $seed, + ), + 'isolated, name, travis' => array( + ISessionStrategyFactory::TYPE_ISOLATED, 'TEST_NAME', 'TRAVIS_BUILD_NUMBER', 'TRAVIS ' . $seed, + ), + 'shared, no name, travis' => array( + ISessionStrategyFactory::TYPE_SHARED, + self::AUTOMATIC_TEST_NAME, + 'TRAVIS_BUILD_NUMBER', + 'TRAVIS ' . $seed, + ), + 'isolated, name, no build' => array(ISessionStrategyFactory::TYPE_ISOLATED, 'TEST_NAME'), + 'shared, no name, no build' => array(ISessionStrategyFactory::TYPE_SHARED, self::AUTOMATIC_TEST_NAME), + ); + } + + /** + * Test description. + * + * @param string $driver_type Driver. + * + * @return void + * @dataProvider onTestEndedDataProvider + */ + public function testOnTestEnded($driver_type) + { + $test_case = $this->createTestCase('TEST_NAME'); + + if ( $driver_type == 'selenium' ) { + $driver = m::mock(Selenium2Driver::class); + $driver->shouldReceive('getWebDriverSessionId')->once()->andReturn('SID'); + + $this->apiClient->shouldReceive('updateStatus')->with('SID', true, 'test status message')->once(); + $test_case->shouldReceive('hasFailed')->once()->andReturn(false); // For shared strategy. + $test_case->shouldReceive('getStatusMessage')->once()->andReturn('test status message'); // For shared strategy. + } + else { + $driver = m::mock(DriverInterface::class); + $this->expectException('RuntimeException'); + } + + $session = m::mock(Session::class); + $session->shouldReceive('getDriver')->once()->andReturn($driver); + $session->shouldReceive('isStarted')->once()->andReturn(true); + + $test_case->shouldReceive('getSession')->with(false)->once()->andReturn($session); + + $test_result = new TestResult(); // Can't mock, because it's a final class. + + $this->browser->onTestEnded($test_case, $test_result); + } + + /** + * Returns possible drivers for session creation. + * + * @return array + */ + public static function onTestEndedDataProvider() + { + return array( + array('selenium'), + array('other'), + ); + } + + /** + * @dataProvider sessionStateDataProvider + */ + public function testTestEndedWithoutSession($stopped_or_missing) + { + $test_case = $this->createTestCase('TEST_NAME'); + + if ( $stopped_or_missing ) { + $session = m::mock(Session::class); + $session->shouldReceive('isStarted')->once()->andReturn(false); + $test_case->shouldReceive('getSession')->with(false)->once()->andReturn($session); + } + else { + $test_case->shouldReceive('getSession')->with(false)->once(); + } + + $test_result = new TestResult(); // Can't mock, because it's a final class. + + $this->browser->onTestEnded($test_case, $test_result); + } + + public static function sessionStateDataProvider() + { + return array( + 'session stopped/missing' => array(true), + 'session started' => array(false), + ); + } + + /** + * Create TestCase with Browser. + * + * @param string $name Test case name. + * + * @return BrowserTestCase|m\MockInterface + */ + protected function createTestCase($name) + { + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getName')->andReturn($name); + + return $test_case; + } + + /** + * Test description. + * + * @param string|null $tunnel_id Tunnel ID. + * + * @return void + * @dataProvider tunnelIdentifierDataProvider + */ + public function testTunnelIdentifier($tunnel_id = null) + { + // Reset any global env vars that might be left from previous tests. + $hhvm_hack = defined('HHVM_VERSION') ? '=' : ''; + + putenv('PHPUNIT_MINK_TUNNEL_ID' . $hhvm_hack); + + if ( isset($tunnel_id) ) { + putenv('PHPUNIT_MINK_TUNNEL_ID=' . $tunnel_id); + } + + $this->browser->setSessionStrategy(ISessionStrategyFactory::TYPE_ISOLATED); + + $test_case = $this->createTestCase('TEST_NAME'); + $test_case->shouldReceive('toString')->andReturn('TEST_NAME'); + + $this->browser->onTestSetup($test_case); + + $desired_capabilities = $this->browser->getDesiredCapabilities(); + + if ( isset($tunnel_id) ) { + foreach ( $this->tunnelCapabilities as $name => $value ) { + if ( substr($value, 0, 4) === 'env:' ) { + $value = getenv(substr($value, 4)); + } + + $this->assertArrayHasKey($name, $desired_capabilities); + $this->assertEquals($value, $desired_capabilities[$name]); + } + } + else { + foreach ( array_keys($this->tunnelCapabilities) as $name ) { + $this->assertArrayNotHasKey($name, $desired_capabilities); + } + } + } + + /** + * Provides Travis job numbers. + * + * @return array + */ + public static function tunnelIdentifierDataProvider() + { + return array( + array('AAA'), + array(null), + ); + } + + public function testGetAPIClient() + { + $this->assertSame($this->apiClient, $this->browser->getAPIClient()); + } + + /** + * Creates instance of browser configuration. + * + * @return ApiBrowserConfiguration + */ + protected function createBrowserConfiguration() + { + /** @var ApiBrowserConfiguration $browser */ + $browser = new $this->browserConfigurationClass($this->driverFactoryRegistry, $this->apiClientFactory); + $browser->setAliases(); + + return $browser; + } + + /** + * Determines if test needs an API client. + * + * @return boolean + */ + protected function needsAPIClient() + { + return in_array($this->getName(false), array('testOnTestEnded', 'testGetAPIClient')); + } + +} diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactoryTest.php b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactoryTest.php new file mode 100644 index 0000000..ade1567 --- /dev/null +++ b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationFactoryTest.php @@ -0,0 +1,138 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfigurationFactory; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\BrowserTestCase; + +class BrowserConfigurationFactoryTest extends AbstractTestCase +{ + + use ExpectException; + + /** + * Browser configuration factory. + * + * @var BrowserConfigurationFactory + */ + private $_factory; + + /** + * @before + */ + protected function setUpTest() + { + $this->_factory = new BrowserConfigurationFactory(); + } + + /** + * Test description. + * + * @param array $browser_config Browser configuration. + * @param string $type Type. + * + * @return void + * @medium + * @dataProvider createBrowserConfigurationDataProvider + */ + public function testCreateBrowserConfiguration(array $browser_config, $type) + { + $browser_aliases = array('alias-one' => array()); + + $test_case = m::mock(BrowserTestCase::class); + $test_case->shouldReceive('getBrowserAliases')->once()->andReturn($browser_aliases); + + $cleaned_browser_config = $browser_config; + unset($cleaned_browser_config['type']); + + $browser_configuration = $this->_createBrowserConfiguration($type); + $browser_configuration + ->shouldReceive('setAliases') + ->with($browser_aliases) + ->once() + ->andReturn($browser_configuration); + $browser_configuration + ->shouldReceive('setup') + ->with($cleaned_browser_config) + ->once() + ->andReturn($browser_configuration); + $this->_factory->register($browser_configuration); + + $actual_browser = $this->_factory->createBrowserConfiguration($browser_config, $test_case); + $this->assertEquals($type, $actual_browser->getType()); + $this->assertInstanceOf(BrowserConfiguration::class, $actual_browser); + } + + /** + * Returns data for possible browser configuration creation ways. + * + * @return array + */ + public static function createBrowserConfigurationDataProvider() + { + return array( + array(array('type' => 'test'), 'test'), + array(array(), 'default'), + ); + } + + /** + * Test description. + * + * @return void + */ + public function testCreateBrowserConfigurationError() + { + $this->expectException('InvalidArgumentException'); + + $browser_aliases = array('alias-one' => array()); + + $test_case = m::mock(BrowserTestCase::class); + $test_case->shouldReceive('getBrowserAliases')->once()->andReturn($browser_aliases); + + $this->_factory->createBrowserConfiguration(array('type' => 'test'), $test_case); + } + + /** + * Test description. + * + * @return void + */ + public function testRegisterFailure() + { + $this->expectException('InvalidArgumentException'); + + $browser_configuration = $this->_createBrowserConfiguration('new-one'); + $this->_factory->register($browser_configuration); + $this->_factory->register($browser_configuration); + } + + /** + * Creates browser configuration. + * + * @param string $type Type. + * + * @return BrowserConfiguration + */ + private function _createBrowserConfiguration($type) + { + $browser_configuration = m::mock(BrowserConfiguration::class); + $browser_configuration->shouldReceive('getType')->andReturn($type); + + return $browser_configuration; + } + +} diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationTest.php b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationTest.php index db53c10..bfad600 100644 --- a/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationTest.php +++ b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserConfigurationTest.php @@ -8,17 +8,40 @@ * @link https://github.com/aik099/phpunit-mink */ -namespace tests\aik099\PHPUnit; +namespace tests\aik099\PHPUnit\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\ApiBrowserConfiguration; use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; use aik099\PHPUnit\BrowserTestCase; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use tests\aik099\PHPUnit\Fixture\WithBrowserConfig; +use tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; -class BrowserConfigurationTest extends \PHPUnit_Framework_TestCase +class BrowserConfigurationTest extends AbstractTestCase { + use ExpectException; + + const TEST_CASE_CLASS = BrowserTestCase::class; + + const HOST = 'example_host'; + + const PORT = 1234; + + /** + * Browser configuration class. + * + * @var string + */ + protected $browserConfigurationClass = ''; + /** * Hostname. * @@ -43,88 +66,162 @@ class BrowserConfigurationTest extends \PHPUnit_Framework_TestCase /** * Browser configuration. * - * @var BrowserConfiguration + * @var BrowserConfiguration|ApiBrowserConfiguration */ protected $browser; /** - * Configures all tests. + * Browser methods, that needs to be mocked. * - * @return void + * @var array */ - protected function setUp() - { - parent::setUp(); + protected $mockBrowserMethods = array(); - $this->host = 'example_host'; - $this->port = 1234; + /** + * Driver factory registry. + * + * @var DriverFactoryRegistry|m\MockInterface + */ + protected $driverFactoryRegistry; + + /** + * @before + */ + protected function setUpTest() + { + if ( !$this->browserConfigurationClass ) { + $this->browserConfigurationClass = BrowserConfiguration::class; + } $this->setup = array( - 'host' => $this->host, - 'port' => $this->port, + 'host' => self::HOST, + 'port' => self::PORT, 'timeout' => 500, 'browserName' => 'safari', - 'desiredCapabilities' => array('platform' => 'Windows 7', 'version' => 10), + 'desiredCapabilities' => array('platform' => 'Windows 10', 'version' => 10), 'baseUrl' => 'http://other-host', - 'sessionStrategy' => SessionStrategyManager::SHARED_STRATEGY, + 'sessionStrategy' => ISessionStrategyFactory::TYPE_SHARED, + 'driver' => 'zombie', + 'driverOptions' => array('customSetting' => 'customValue'), ); + $this->driverFactoryRegistry = $this->createDriverFactoryRegistry(); + $this->browser = $this->createBrowserConfiguration(); } /** - * Test description. + * Creates driver factory registry. * - * @return void + * @return DriverFactoryRegistry */ - public function testResolveAliasesUsingSingleAlias() + protected function createDriverFactoryRegistry() { - $browser = $this->createBrowserConfiguration(array( - 'a1' => array('host' => $this->host, 'port' => $this->port), + $registry = m::mock(DriverFactoryRegistry::class); + + $selenium2_driver_factory = m::mock(IMinkDriverFactory::class); + $selenium2_driver_factory->shouldReceive('getDriverDefaults')->andReturn(array( + 'baseUrl' => 'http://www.super-url.com', + 'driverOptions' => array( + 'driverParam1' => 'driverParamValue1', + ), )); - - $browser->setup(array('alias' => 'a1')); - - $this->assertEquals($this->host, $browser->getHost()); - $this->assertEquals($this->port, $browser->getPort()); + $registry + ->shouldReceive('get') + ->with('selenium2') + ->andReturn($selenium2_driver_factory); + + $zombie_driver_factory = m::mock(IMinkDriverFactory::class); + $zombie_driver_factory->shouldReceive('getDriverDefaults')->andReturn(array()); + $registry + ->shouldReceive('get') + ->with('zombie') + ->andReturn($zombie_driver_factory); + + return $registry; } /** * Test description. * + * @param array $aliases Test case aliases. + * @param array $browser_config Browser config. + * @param array $expected_config Expected browser config. + * * @return void + * @dataProvider aliasResolutionDataProvider */ - public function testResolveAliasesUsingRecursiveAlias() + public function testAliasResolution(array $aliases, array $browser_config, array $expected_config) { - $browser = $this->createBrowserConfiguration(array( - 'a1' => array('alias' => 'a2', 'host' => $this->host, 'port' => $this->port), - 'a2' => array('browserName' => 'safari', 'baseUrl' => 'http://example_host'), - )); - - $browser->setup(array('alias' => 'a1')); + $this->assertSame($this->browser, $this->browser->setAliases($aliases)); + $this->browser->setup($browser_config); - $this->assertEquals($this->host, $browser->getHost()); - $this->assertEquals($this->port, $browser->getPort()); - $this->assertEquals('safari', $browser->getBrowserName()); - $this->assertEquals('http://example_host', $browser->getBaseUrl()); + $this->assertEquals($expected_config['host'], $this->browser->getHost()); + $this->assertEquals($expected_config['port'], $this->browser->getPort()); + $this->assertEquals($expected_config['browserName'], $this->browser->getBrowserName()); + $this->assertEquals($expected_config['baseUrl'], $this->browser->getBaseUrl()); } /** - * Test description. + * Alias resolution checking data provider. * - * @return void + * @return array */ - public function testResolveAliasesUsingAliasMerging() + public static function aliasResolutionDataProvider() { - $browser = $this->createBrowserConfiguration(array( - 'a1' => array('host' => $this->host, 'port' => $this->port), - )); - - $browser->setup(array('alias' => 'a1', 'browserName' => 'firefox')); - - $this->assertEquals($this->host, $browser->getHost()); - $this->assertEquals($this->port, $browser->getPort()); - $this->assertEquals('firefox', $browser->getBrowserName()); + return array( + 'single alias' => array( + array( + 'a1' => array('host' => static::HOST, 'port' => static::PORT), + ), + array('alias' => 'a1'), + array( + 'host' => static::HOST, 'port' => static::PORT, 'browserName' => 'firefox', + 'baseUrl' => 'http://www.super-url.com', // Comes from driver defaults. + ), + ), + 'recursive alias' => array( + array( + 'a1' => array('alias' => 'a2', 'host' => static::HOST, 'port' => static::PORT), + 'a2' => array('browserName' => 'safari', 'baseUrl' => 'http://example_host'), + ), + array('alias' => 'a1'), + array( + 'host' => static::HOST, + 'port' => static::PORT, + 'browserName' => 'safari', + 'baseUrl' => 'http://example_host', + ), + ), + 'alias merging' => array( + array( + 'a1' => array('host' => static::HOST, 'port' => static::PORT), + ), + array('alias' => 'a1', 'browserName' => 'firefox'), + array( + 'host' => static::HOST, 'port' => static::PORT, 'browserName' => 'firefox', + 'baseUrl' => 'http://www.super-url.com', // Comes from driver defaults. + ), + ), + 'with overwrite' => array( + array( + 'a1' => array('host' => 'alias-host', 'port' => static::PORT), + ), + array('alias' => 'a1', 'host' => static::HOST), + array( + 'host' => static::HOST, 'port' => static::PORT, 'browserName' => 'firefox', + 'baseUrl' => 'http://www.super-url.com', // Comes from driver defaults. + ), + ), + 'without alias given' => array( + array(), + array('host' => static::HOST, 'port' => static::PORT, 'browserName' => 'safari'), + array( + 'host' => static::HOST, 'port' => static::PORT, 'browserName' => 'safari', + 'baseUrl' => 'http://www.super-url.com', // Comes from driver defaults. + ), + ), + ); } /** @@ -132,26 +229,38 @@ public function testResolveAliasesUsingAliasMerging() * * @return void */ - public function testResolveAliasesWithOverwrite() + public function testResolveAliasesUsingIncorrectAlias() { - $browser = $this->createBrowserConfiguration(array( - 'a1' => array('host' => 'alias-host', 'port' => $this->port), - )); - - $browser->setup(array('alias' => 'a1', 'host' => $this->host)); + $this->expectException('InvalidArgumentException'); - $this->assertEquals($this->host, $browser->getHost()); + $this->browser->setup(array('alias' => 'not_found')); } /** * Test description. * * @return void - * @expectedException \InvalidArgumentException */ - public function testResolveAliasesUsingIncorrectAlias() + public function testSetup() { - $this->browser->setup(array('alias' => 'not_found')); + $this->assertSame($this->browser, $this->browser->setup($this->setup)); + $this->assertSame($this->setup['host'], $this->browser->getHost()); + $this->assertSame($this->setup['port'], $this->browser->getPort()); + $this->assertSame($this->setup['timeout'], $this->browser->getTimeout()); + $this->assertSame($this->setup['browserName'], $this->browser->getBrowserName()); + $this->assertSame( + $this->setup['desiredCapabilities'], + $this->browser->getDesiredCapabilities(), + 'The browser\'s desired capabilities weren\'t set to the test case.' + ); + $this->assertSame($this->setup['baseUrl'], $this->browser->getBaseUrl()); + $this->assertSame($this->setup['sessionStrategy'], $this->browser->getSessionStrategy()); + $this->assertSame($this->setup['driver'], $this->browser->getDriver()); + $this->assertSame( + $this->setup['driverOptions'], + $this->browser->getDriverOptions(), + 'The browser\'s driver options weren\'t set to the test case.' + ); } /** @@ -159,38 +268,62 @@ public function testResolveAliasesUsingIncorrectAlias() * * @return void */ - public function testResolveAliasesWithoutAliasGiven() + public function testSetupScreamsAboutUnknownParameters() { - $this->browser->setup(array('browserName' => 'safari')); + $this->expectException('InvalidArgumentException'); - $this->assertEquals('safari', $this->browser->getBrowserName()); + $this->browser->setup(array('unknown-parameter' => 'value')); } - /** - * Test description. - * - * @return void - */ - public function testSetup() + public function testGetType() { - $this->assertSame($this->browser, $this->browser->setup($this->setup)); - $this->assertSame($this->setup['host'], $this->browser->getHost()); - $this->assertSame($this->setup['port'], $this->browser->getPort()); - $this->assertSame($this->setup['timeout'], $this->browser->getTimeout()); - $this->assertSame($this->setup['browserName'], $this->browser->getBrowserName()); - $this->assertSame($this->setup['desiredCapabilities'], $this->browser->getDesiredCapabilities()); - $this->assertSame($this->setup['baseUrl'], $this->browser->getBaseUrl()); - $this->assertSame($this->setup['sessionStrategy'], $this->browser->getSessionStrategy()); + $this->assertEquals('default', $this->browser->getType()); + } + + public function testSetDriverIncorrect() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('The Mink driver name must be a string'); + + $this->browser->setDriver(array()); + } + + public function testSetDriverCorrect() + { + $expected = 'zombie'; + $this->assertEquals($this->browser, $this->browser->setDriver($expected)); + $this->assertSame($expected, $this->browser->getDriver()); + } + + public function testSetDriverOptionsCorrect() + { + $user_driver_options = array('o1' => 'v1', 'o2' => 'v2'); + $expected_driver_options = array('driverParam1' => 'driverParamValue1') + $user_driver_options; + + $this->assertSame($this->browser, $this->browser->setDriverOptions($user_driver_options)); + $this->assertSame( + $expected_driver_options, + $this->browser->getDriverOptions(), + 'The browser driver options wren\'t set correctly.' + ); + } + + public function testDriverFactoryDefaultsApplied() + { + $this->browser->setup(array('driver' => 'selenium2')); + + $this->assertEquals('http://www.super-url.com', $this->browser->getBaseUrl()); } /** * Test description. * * @return void - * @expectedException \InvalidArgumentException */ public function testSetHostIncorrect() { + $this->expectException('InvalidArgumentException'); + $this->browser->setHost(5555); } @@ -210,10 +343,11 @@ public function testSetHostCorrect() * Test description. * * @return void - * @expectedException \InvalidArgumentException */ public function testSetPortIncorrect() { + $this->expectException('InvalidArgumentException'); + $this->browser->setPort('5555'); } @@ -233,10 +367,11 @@ public function testSetPortCorrect() * Test description. * * @return void - * @expectedException \InvalidArgumentException */ public function testSetBrowserNameIncorrect() { + $this->expectException('InvalidArgumentException'); + $this->browser->setBrowserName(5555); } @@ -256,10 +391,11 @@ public function testSetBrowserNameCorrect() * Test description. * * @return void - * @expectedException \InvalidArgumentException */ public function testSetBaseUrlIncorrect() { + $this->expectException('InvalidArgumentException'); + $this->browser->setBaseUrl(5555); } @@ -282,12 +418,17 @@ public function testSetBaseUrlCorrect() * @param array|null $expected Expected capabilities. * * @return void + * @see SauceLabsBrowserConfigurationTest::testSetDesiredCapabilitiesCorrect() */ public function testSetDesiredCapabilitiesCorrect(array $desired_capabilities = null, array $expected = null) { $expected = array('k1' => 'v1', 'k2' => 'v2'); $this->assertSame($this->browser, $this->browser->setDesiredCapabilities($expected)); - $this->assertSame($expected, $this->browser->getDesiredCapabilities()); + $this->assertSame( + $expected, + $this->browser->getDesiredCapabilities(), + 'The browser desired capabilities wren\'t set correctly.' + ); } /** @@ -295,45 +436,64 @@ public function testSetDesiredCapabilitiesCorrect(array $desired_capabilities = * * @return void */ - public function testSetTimeoutCorrect() + public function testSetTimeoutIncorrect() { - $expected = 1000; - $this->assertSame($this->browser, $this->browser->setTimeout($expected)); - $this->assertSame($expected, $this->browser->getTimeout()); + $this->expectException('InvalidArgumentException'); + + $this->browser->setTimeout('5555'); } /** * Test description. * * @return void - * @expectedException \InvalidArgumentException */ - public function testSetTimeoutIncorrect() + public function testSetTimeoutCorrect() { - $this->browser->setTimeout('5555'); + $expected = 1000; + $this->assertSame($this->browser, $this->browser->setTimeout($expected)); + $this->assertSame($expected, $this->browser->getTimeout()); } /** * Test description. * + * @param string $expected Expected strategy. + * * @return void + * @dataProvider sessionSharingDataProvider */ - public function testSetSessionStrategyCorrect() + public function testSetSessionStrategy($expected) { - $expected = SessionStrategyManager::SHARED_STRATEGY; $this->assertSame($this->browser, $this->browser->setSessionStrategy($expected)); $this->assertSame($expected, $this->browser->getSessionStrategy()); } - /** - * Test description. - * - * @return void - * @expectedException \InvalidArgumentException - */ - public function testSetSessionStrategyIncorrect() + public function testGetParameterIncorrect() { - $this->browser->setSessionStrategy('wrong'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Unable to get unknown parameter "nonExisting"'); + + $this->browser->getNonExisting(); + } + + public function testCreateDriver() + { + /** @var m\MockInterface $driver_factory */ + $driver_factory = $this->driverFactoryRegistry->get('selenium2'); + $driver_factory->shouldReceive('createDriver')->with($this->browser)->once()->andReturn('OK'); + + $this->assertEquals('OK', $this->browser->createDriver()); + } + + public function testNonExistingMethod() + { + $this->expectException('BadMethodCallException'); + $this->expectExceptionMessage( + 'Method "nonExistingMethod" does not exist on ' . get_class($this->browser) . ' class' + ); + + $this->browser->nonExistingMethod(); } /** @@ -346,8 +506,8 @@ public function testSetSessionStrategyIncorrect() */ public function testGetSessionStrategyHashBrowserSharing($session_strategy) { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - /* @var $test_case BrowserTestCase */ + /** @var BrowserTestCase $test_case */ + $test_case = m::mock(self::TEST_CASE_CLASS); $browser1 = $this->createBrowserConfiguration(); $browser1->setSessionStrategy($session_strategy); @@ -355,7 +515,10 @@ public function testGetSessionStrategyHashBrowserSharing($session_strategy) $browser2 = $this->createBrowserConfiguration(); $browser2->setSessionStrategy($session_strategy); - $this->assertSame($browser1->getSessionStrategyHash($test_case), $browser2->getSessionStrategyHash($test_case)); + $this->assertSame( + $browser1->getSessionStrategyHash($test_case), + $browser2->getSessionStrategyHash($test_case) + ); } /** @@ -363,11 +526,11 @@ public function testGetSessionStrategyHashBrowserSharing($session_strategy) * * @return array */ - public function sessionSharingDataProvider() + public static function sessionSharingDataProvider() { return array( - array(SessionStrategyManager::ISOLATED_STRATEGY), - array(SessionStrategyManager::SHARED_STRATEGY), + array(ISessionStrategyFactory::TYPE_ISOLATED), + array(ISessionStrategyFactory::TYPE_SHARED), ); } @@ -378,19 +541,18 @@ public function sessionSharingDataProvider() */ public function testGetSessionStrategyHashNotSharing() { - $test_case1 = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - /* @var $test_case1 BrowserTestCase */ - + $test_case1 = new WithBrowserConfig('test name'); $browser1 = $this->createBrowserConfiguration(); - $browser1->setSessionStrategy(SessionStrategyManager::SHARED_STRATEGY); - - $test_case2 = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - /* @var $test_case2 BrowserTestCase */ + $browser1->setSessionStrategy(ISessionStrategyFactory::TYPE_SHARED); + $test_case2 = new WithoutBrowserConfig('test name'); $browser2 = $this->createBrowserConfiguration(); - $browser2->setSessionStrategy(SessionStrategyManager::SHARED_STRATEGY); + $browser2->setSessionStrategy(ISessionStrategyFactory::TYPE_SHARED); - $this->assertNotSame($browser1->getSessionStrategyHash($test_case1), $browser2->getSessionStrategyHash($test_case2)); + $this->assertNotSame( + $browser1->getSessionStrategyHash($test_case1), + $browser2->getSessionStrategyHash($test_case2) + ); } /** @@ -400,11 +562,11 @@ public function testGetSessionStrategyHashNotSharing() */ public function testGetTestStatusIsolated() { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); + $test_case = m::mock(self::TEST_CASE_CLASS); $test_case->shouldReceive('hasFailed')->once()->andReturn(false); - $test_result = m::mock('\\PHPUnit_Framework_TestResult'); + $test_result = new TestResult(); // Can't mock, because it's a final class. - $this->browser->setSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY); + $this->browser->setSessionStrategy(ISessionStrategyFactory::TYPE_ISOLATED); $this->assertTrue($this->browser->getTestStatus($test_case, $test_result)); } @@ -415,66 +577,47 @@ public function testGetTestStatusIsolated() */ public function testGetTestStatusShared() { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - $test_result = m::mock('\\PHPUnit_Framework_TestResult'); - $test_result->shouldReceive('wasSuccessful')->once()->andReturn(true); + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_result = new TestResult(); // Can't mock, because it's a final class. - $this->browser->setSessionStrategy(SessionStrategyManager::SHARED_STRATEGY); + $this->browser->setSessionStrategy(ISessionStrategyFactory::TYPE_SHARED); $this->assertTrue($this->browser->getTestStatus($test_case, $test_result)); } - /** - * Test description. - * - * @return void - */ - public function testCreateSession() + public function testChecksumMatch() { - $session = $this->browser->createSession(); - - $this->assertInstanceOf('\\Behat\\Mink\\Session', $session); - $this->assertInstanceOf('\\Behat\\Mink\\Driver\\Selenium2Driver', $session->getDriver()); - } + $browser1 = $this->createBrowserConfiguration(); + $browser1->setBrowserName('opera'); - /** - * Test description. - * - * @return void - */ - public function testSetUpHook() - { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - /* @var $test_case BrowserTestCase */ + $browser2 = $this->createBrowserConfiguration(); + $browser2->setBrowserName('opera'); - $this->assertSame($this->browser, $this->browser->testSetUpHook($test_case)); + $this->assertSame($browser1->getChecksum(), $browser2->getChecksum()); } - /** - * Test description. - * - * @return void - */ - public function testAfterRunHook() + public function testChecksumMismatch() { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - /* @var $test_case BrowserTestCase */ + $browser1 = $this->createBrowserConfiguration(); + $browser1->setBrowserName('opera'); - $test_result = m::mock('\\PHPUnit_Framework_TestResult'); - /* @var $test_result \PHPUnit_Framework_TestResult */ + $browser2 = $this->createBrowserConfiguration(); + $browser2->setBrowserName('firefox'); - $this->assertSame($this->browser, $this->browser->testAfterRunHook($test_case, $test_result)); + $this->assertNotSame($browser1->getChecksum(), $browser2->getChecksum()); } /** * Creates instance of browser configuration. * - * @param array $aliases Aliases. - * * @return BrowserConfiguration */ - protected function createBrowserConfiguration(array $aliases = array()) + protected function createBrowserConfiguration() { - return new BrowserConfiguration($aliases); + /** @var BrowserConfiguration $browser */ + $browser = new $this->browserConfigurationClass($this->driverFactoryRegistry); + $browser->setAliases(); + + return $browser; } } diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfigurationTest.php b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfigurationTest.php new file mode 100644 index 0000000..c157b55 --- /dev/null +++ b/tests/aik099/PHPUnit/BrowserConfiguration/BrowserStackBrowserConfigurationTest.php @@ -0,0 +1,79 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\BrowserConfiguration; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; + +class BrowserStackBrowserConfigurationTest extends ApiBrowserConfigurationTestCase +{ + + const HOST = ':@hub.browserstack.com'; + + /** + * @before + */ + protected function setUpTest() + { + $this->browserConfigurationClass = BrowserStackBrowserConfiguration::class; + + $this->tunnelCapabilities = array( + 'browserstack.local' => 'true', + 'browserstack.localIdentifier' => 'env:PHPUNIT_MINK_TUNNEL_ID', + ); + + parent::setUpTest(); + + $this->setup['desiredCapabilities'] = array( + 'os' => 'Windows', 'os_version' => 'XP', 'version' => 10, + 'acceptSslCerts' => 'true', + ); + $this->setup['host'] = 'UN:AK@hub.browserstack.com'; + } + + public function testGetType() + { + $this->assertEquals('browserstack', $this->browser->getType()); + } + + /** + * Test description. + * + * @return void + */ + public function testSetHostCorrect() + { + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); + + $this->assertSame($browser, $browser->setHost('EXAMPLE_HOST')); + $this->assertSame('A:B@hub.browserstack.com', $browser->getHost()); + } + + /** + * @inheritDoc + */ + public static function desiredCapabilitiesDataProvider() + { + return array( + array( + array('os' => 'os-name', 'os_version' => 'os-version'), + array('os' => 'os-name', 'os_version' => 'os-version', 'acceptSslCerts' => 'true'), + ), + array( + array('acceptSslCerts' => 'false'), + array('acceptSslCerts' => 'false', 'os' => 'Windows', 'os_version' => '10'), + ), + ); + } + +} diff --git a/tests/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfigurationTest.php b/tests/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfigurationTest.php index 5937cf9..2d97375 100644 --- a/tests/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfigurationTest.php +++ b/tests/aik099/PHPUnit/BrowserConfiguration/SauceLabsBrowserConfigurationTest.php @@ -8,71 +8,38 @@ * @link https://github.com/aik099/phpunit-mink */ -namespace tests\aik099\PHPUnit; +namespace tests\aik099\PHPUnit\BrowserConfiguration; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; -use WebDriver\SauceLabs\Capability as SauceLabsCapability; use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; -use aik099\PHPUnit\BrowserTestCase; -use Mockery as m; -class SauceLabsBrowserConfigurationTest extends BrowserConfigurationTest +class SauceLabsBrowserConfigurationTest extends ApiBrowserConfigurationTestCase { - /** - * Configures all tests. - * - * @return void - */ - protected function setUp() - { - parent::setUp(); - - $this->host = ':@ondemand.saucelabs.com'; - $this->port = 80; - - $this->setup['host'] = 'UN:AK@ondemand.saucelabs.com'; - $this->setup['port'] = 80; - $this->setup['sauce'] = array('username' => 'UN', 'api_key' => 'AK'); - } + const HOST = ':@ondemand.saucelabs.com'; /** - * Test description. - * - * @return void + * @before */ - public function testSetup() + protected function setUpTest() { - parent::testSetup(); + $this->browserConfigurationClass = SauceLabsBrowserConfiguration::class; - $this->assertSame($this->setup['sauce'], $this->browser->getSauce()); - } + $this->tunnelCapabilities = array( + 'tunnel-identifier' => 'env:PHPUNIT_MINK_TUNNEL_ID', + ); - /** - * Test description. - * - * @return void - * @expectedException \InvalidArgumentException - */ - public function testSetSauceIncorrect() - { - $browser = $this->createBrowserConfiguration(); - $browser->setSauce(array()); + parent::setUpTest(); + + $this->setup['desiredCapabilities'] = array( + 'platform' => 'Windows 10', 'acceptInsecureCerts' => true, + ); + $this->setup['host'] = 'UN:AK@ondemand.saucelabs.com'; } - /** - * Test description. - * - * @return void - */ - public function testSetSauceCorrect() + public function testGetType() { - $expected = array('username' => '', 'api_key' => ''); - $browser = $this->createBrowserConfiguration(); - - $this->assertSame($browser, $browser->setSauce($expected)); - $this->assertSame($expected, $browser->getSauce()); + $this->assertEquals('saucelabs', $this->browser->getType()); } /** @@ -82,190 +49,29 @@ public function testSetSauceCorrect() */ public function testSetHostCorrect() { - $browser = $this->createBrowserConfiguration(array(), true); + $browser = $this->createBrowserConfiguration(); + $browser->setApiUsername('A'); + $browser->setApiKey('B'); $this->assertSame($browser, $browser->setHost('EXAMPLE_HOST')); $this->assertSame('A:B@ondemand.saucelabs.com', $browser->getHost()); } /** - * Test description. - * - * @return void - */ - public function testSetPortCorrect() - { - $browser = $this->createBrowserConfiguration(array(), true); - $this->assertSame($browser, $browser->setPort(5555)); - $this->assertSame(80, $browser->getPort()); - } - - /** - * Test description. - * - * @return void + * @inheritDoc */ - public function testSetBrowserNameCorrect() - { - $browser = $this->createBrowserConfiguration(array(), true); - $this->assertSame($browser, $browser->setBrowserName('')); - $this->assertSame('chrome', $browser->getBrowserName()); - } - - /** - * Test description. - * - * @param array|null $desired_capabilities Desired capabilities. - * @param array|null $expected Expected capabilities. - * - * @return void - * @dataProvider desiredCapabilitiesDataProvider - */ - public function testSetDesiredCapabilitiesCorrect(array $desired_capabilities = null, array $expected = null) - { - $browser = $this->createBrowserConfiguration(array(), true); - $this->assertSame($browser, $browser->setDesiredCapabilities($desired_capabilities)); - $this->assertSame($expected, $browser->getDesiredCapabilities()); - } - - /** - * Desired capability data provider. - * - * @return array - */ - public function desiredCapabilitiesDataProvider() + public static function desiredCapabilitiesDataProvider() { return array( array( array('platform' => 'pl1'), - array('platform' => 'pl1', 'version' => ''), + array('platform' => 'pl1', 'acceptInsecureCerts' => true), ), array( array('version' => 'ver1'), - array('version' => 'ver1', 'platform' => 'Windows XP'), + array('version' => 'ver1', 'platform' => 'Windows 10', 'acceptInsecureCerts' => true), ), ); } - /** - * Test description. - * - * @return void - */ - public function testSetUpHook() - { - $this->markTestSkipped('Other more complex tests cover this'); - } - - /** - * Test description. - * - * @param string $session_strategy Session strategy. - * @param string $expected Expected job name. - * - * @return void - * @dataProvider jobNameDataProvider - * @covers \aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration::testSetUpHook - */ - public function testJobName($session_strategy, $expected) - { - /* @var $test_case BrowserTestCase */ - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - - if ( !isset($expected) ) { - $expected = get_class($test_case); - } - - $this->browser->setSessionStrategy($session_strategy); - $test_case->shouldReceive('toString')->andReturn($expected); - - $this->browser->testSetUpHook($test_case); - - $capabilities = $this->browser->getDesiredCapabilities(); - $this->assertArrayHasKey(SauceLabsCapability::NAME, $capabilities); - $this->assertSame($expected, $capabilities[SauceLabsCapability::NAME]); - } - - /** - * JobName data provider. - * - * @return array - */ - public function jobNameDataProvider() - { - return array( - array(SessionStrategyManager::ISOLATED_STRATEGY, 'TEST_NAME'), - array(SessionStrategyManager::SHARED_STRATEGY, null), - ); - } - - /** - * Test description. - * - * @return void - * @covers \aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration::testSetUpHook - */ - public function testBuildNumberPresent() - { - /* @var $test_case BrowserTestCase */ - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - $this->browser->setSessionStrategy(SessionStrategyManager::SHARED_STRATEGY); - - $expected = 'X'; - putenv('BUILD_NUMBER=' . $expected); - $this->browser->testSetUpHook($test_case); - putenv('BUILD_NUMBER'); - - $capabilities = $this->browser->getDesiredCapabilities(); - $this->assertArrayHasKey(SauceLabsCapability::BUILD, $capabilities); - $this->assertSame($expected, $capabilities[SauceLabsCapability::BUILD]); - } - - /** - * Test description. - * - * @return void - * @covers \aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration::testSetUpHook - */ - public function testBuildNumberAbsent() - { - /* @var $test_case BrowserTestCase */ - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase'); - $this->browser->setSessionStrategy(SessionStrategyManager::SHARED_STRATEGY); - - $this->browser->testSetUpHook($test_case); - - $capabilities = $this->browser->getDesiredCapabilities(); - $this->assertArrayNotHasKey(SauceLabsCapability::BUILD, $capabilities); - } - - /** - * Test description. - * - * @return void - */ - public function testAfterRunHook() - { - $this->markTestSkipped('TODO'); - } - - /** - * Creates instance of browser configuration. - * - * @param array $aliases Aliases. - * @param boolean $with_sauce Include test sauce configuration. - * - * @return SauceLabsBrowserConfiguration - */ - protected function createBrowserConfiguration(array $aliases = array(), $with_sauce = false) - { - $browser = new SauceLabsBrowserConfiguration($aliases); - - if ( $with_sauce ) { - $browser->setSauce(array('username' => 'A', 'api_key' => 'B')); - } - - return $browser; - } - } diff --git a/tests/aik099/PHPUnit/BrowserTestCaseTest.php b/tests/aik099/PHPUnit/BrowserTestCaseTest.php index c39039b..f43fe32 100644 --- a/tests/aik099/PHPUnit/BrowserTestCaseTest.php +++ b/tests/aik099/PHPUnit/BrowserTestCaseTest.php @@ -12,21 +12,60 @@ use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; use aik099\PHPUnit\BrowserTestCase; -use aik099\PHPUnit\SessionStrategy\ISessionStrategy; -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageTool; +use aik099\PHPUnit\Session\ISessionStrategy; +use aik099\PHPUnit\Session\SessionStrategyManager; +use ConsoleHelpers\CodeCoverageCompat\CodeCoverage; +use ConsoleHelpers\CodeCoverageCompat\Filter; +use ConsoleHelpers\PHPUnitCompat\Framework\SkippedTestError; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; use Mockery as m; +use Mockery\MockInterface; +use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; use tests\aik099\PHPUnit\Fixture\WithBrowserConfig; use tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; +use Behat\Mink\Session; +use ConsoleHelpers\CodeCoverageCompat\Driver\Driver; +use Behat\Mink\Exception\DriverException; -class BrowserTestCaseTest extends \PHPUnit_Framework_TestCase +class BrowserTestCaseTest extends BrowserTestCase { - const BROWSER_CLASS = '\\aik099\\PHPUnit\\BrowserConfiguration\\BrowserConfiguration'; + use ExpectException; + use TVerifyTestExpectations; - const MANAGER_CLASS = '\\aik099\\PHPUnit\\SessionStrategy\\SessionStrategyManager'; + const BROWSER_CLASS = BrowserConfiguration::class; - const SESSION_STRATEGY_INTERFACE = '\\aik099\\PHPUnit\\SessionStrategy\\ISessionStrategy'; + const MANAGER_CLASS = SessionStrategyManager::class; + + const SESSION_STRATEGY_INTERFACE = ISessionStrategy::class; + + /** + * Browser configuration factory. + * + * @var IBrowserConfigurationFactory|MockInterface + */ + protected $browserConfigurationFactory; + + /** + * @before + */ + protected function setUpTest() + { + // Define the constant because this test is running PHPUnit testcases manually. + if ( $this->isInIsolation() ) { + define('PHPUNIT_TESTSUITE', true); + } + + $this->browserConfigurationFactory = m::mock(IBrowserConfigurationFactory::class); + } /** * Test description. @@ -35,13 +74,18 @@ class BrowserTestCaseTest extends \PHPUnit_Framework_TestCase */ public function testSetSessionStrategyManager() { - /* @var $manager \aik099\PHPUnit\SessionStrategy\SessionStrategyManager */ + /** @var SessionStrategyManager $manager */ $manager = m::mock(self::MANAGER_CLASS); - $test_case = new WithoutBrowserConfig(); - $test_case->setSessionStrategyManager($manager); + $test_case = new WithoutBrowserConfig('test name'); - $property = new \ReflectionProperty($test_case, 'sessionStrategyManager'); + $this->assertSame( + $test_case, + $test_case->setSessionStrategyManager($manager), + 'The fluid interface doesn\'t work.' + ); + + $property = new \ReflectionProperty(BrowserTestCase::class, '_sessionStrategyManager'); $property->setAccessible(true); $this->assertSame($manager, $property->getValue($test_case)); @@ -54,11 +98,12 @@ public function testSetSessionStrategyManager() */ public function testSetBrowserCorrect() { + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - /* @var $session_strategy ISessionStrategy */ - $browser = new BrowserConfiguration(); - $test_case = $this->getFixture($session_strategy); + $test_case = $this->getFixture(true, $session_strategy); + + $browser = new BrowserConfiguration($this->createDriverFactoryRegistry()); $this->assertSame($test_case, $test_case->setBrowser($browser)); $this->assertSame($browser, $test_case->getBrowser()); @@ -66,30 +111,24 @@ public function testSetBrowserCorrect() } /** - * Test description. + * Creates driver factory registry. * - * @return void - * @expectedException \RuntimeException + * @return DriverFactoryRegistry */ - public function testGetBrowserNotSpecified() + protected function createDriverFactoryRegistry() { - $test_case = new WithoutBrowserConfig(); - $test_case->getBrowser(); - } + $registry = m::mock(DriverFactoryRegistry::class); - /** - * Test description. - * - * @return void - */ - public function testSetBrowserFromConfigurationDefault() - { - $test_case = $this->getFixture(); - $this->assertSame($test_case, $test_case->setBrowserFromConfiguration(array( - 'browserName' => 'safari', - ))); + $driver_factory = m::mock(IMinkDriverFactory::class); + $driver_factory->shouldReceive('getDriverDefaults')->andReturn(array()); - $this->assertInstanceOf(self::BROWSER_CLASS, $test_case->getBrowser()); + $registry + ->shouldReceive('get') + ->with('selenium2') + ->once() + ->andReturn($driver_factory); + + return $registry; } /** @@ -97,15 +136,12 @@ public function testSetBrowserFromConfigurationDefault() * * @return void */ - public function testSetBrowserFromConfigurationWithSauce() + public function testGetBrowserNotSpecified() { - $test_case = $this->getFixture(); - $this->assertSame($test_case, $test_case->setBrowserFromConfiguration(array( - 'browserName' => 'safari', 'sauce' => array('username' => 'test-user', 'api_key' => 'ABC'), - ))); + $this->expectException('RuntimeException'); - $expected = '\\aik099\\PHPUnit\\BrowserConfiguration\\SauceLabsBrowserConfiguration'; - $this->assertInstanceOf($expected, $test_case->getBrowser()); + $test_case = new WithoutBrowserConfig('test name'); + $test_case->getBrowser(); } /** @@ -113,15 +149,22 @@ public function testSetBrowserFromConfigurationWithSauce() * * @return void */ - public function testSetBrowserFromConfigurationStrategy() + public function testSetBrowserFromConfigurationDefault() { - /* @var $test_case BrowserTestCase */ - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase[setBrowser]'); - $test_case->shouldReceive('setBrowser')->once()->andReturn($test_case); + $test_case = $this->getFixture(true); + $test_case->setBrowserConfigurationFactory($this->browserConfigurationFactory); + + $browser = $this->getBrowserMock(0); + $browser_config = array('browserName' => 'safari'); + + $this->browserConfigurationFactory + ->shouldReceive('createBrowserConfiguration') + ->with($browser_config, $test_case) + ->once() + ->andReturn($browser); - $this->assertSame($test_case, $test_case->setBrowserFromConfiguration(array( - 'browserName' => 'safari', - ))); + $this->assertSame($test_case, $test_case->setBrowserFromConfiguration($browser_config)); + $this->assertInstanceOf(self::BROWSER_CLASS, $test_case->getBrowser()); } /** @@ -131,10 +174,10 @@ public function testSetBrowserFromConfigurationStrategy() */ public function testSetSessionStrategy() { - /* @var $session_strategy \aik099\PHPUnit\SessionStrategy\ISessionStrategy */ + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - $test_case = new WithoutBrowserConfig(); + $test_case = new WithoutBrowserConfig('test name'); $this->assertSame($test_case, $test_case->setSessionStrategy($session_strategy)); $this->assertSame($session_strategy, $test_case->getSessionStrategy()); } @@ -147,15 +190,15 @@ public function testSetSessionStrategy() */ public function testGetSessionStrategySharing() { - /* @var $manager \aik099\PHPUnit\SessionStrategy\SessionStrategyManager */ + /** @var SessionStrategyManager $manager */ $manager = m::mock(self::MANAGER_CLASS); $manager->shouldReceive('getDefaultSessionStrategy')->twice()->andReturn('STRATEGY'); - $test_case1 = new WithoutBrowserConfig(); + $test_case1 = new WithoutBrowserConfig('test name'); $test_case1->setSessionStrategyManager($manager); - $test_case2 = new WithBrowserConfig(); + $test_case2 = new WithBrowserConfig('test name'); $test_case2->setSessionStrategyManager($manager); $this->assertSame($test_case1->getSessionStrategy(), $test_case2->getSessionStrategy()); @@ -166,110 +209,140 @@ public function testGetSessionStrategySharing() * * @return void */ - public function testGetSession() + public function testGetSessionWithAutoCreate() { - $browser = $this->getBrowser(); - - $expected_session1 = m::mock('\\Behat\\Mink\\Session'); - $expected_session1->shouldReceive('isStarted')->withNoArgs()->once()->andReturn(false); + $browser = $this->getBrowserMock(0); - $expected_session2 = m::mock('\\Behat\\Mink\\Session'); - $expected_session2->shouldReceive('isStarted')->withNoArgs()->once()->andReturn(true); + $expected_session1 = m::mock(Session::class); + $expected_session2 = m::mock(Session::class); - /* @var $session_strategy ISessionStrategy */ + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); $session_strategy->shouldReceive('session')->with($browser)->andReturn($expected_session1, $expected_session2); - $test_case = $this->getFixture($session_strategy); + $test_case = $this->getFixture(true, $session_strategy); $test_case->setBrowser($browser); - $test_case->setTestResultObject($this->getTestResult($test_case, 0)); + $test_case->setTestResultObject(new TestResult()); - // create session when missing + // Create session when missing. $session1 = $test_case->getSession(); $this->assertSame($expected_session1, $session1); - // create session when present, but stopped + // Always reuse created session. $session2 = $test_case->getSession(); - $this->assertSame($expected_session2, $session2); + $this->assertSame($session1, $session2); + } - // reuse created session, when started - $session3 = $test_case->getSession(); - $this->assertSame($session2, $session3); + public function testGetSessionWithoutAutoCreate() + { + $test_case = $this->getFixture(false); + + $this->assertNull($test_case->getSession(false), 'The session was created upon request.'); } /** * Test description. * * @return void - * @expectedException \Exception - * @expectedExceptionMessage MSG_SKIP */ public function testGetSessionDriverError() { - $browser = $this->getBrowser(); + $browser = $this->getBrowserMock(1); - /* @var $session_strategy ISessionStrategy */ + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - $session_strategy->shouldReceive('session')->andThrow('\Behat\Mink\Exception\DriverException'); + $session_strategy->shouldReceive('session')->andThrow(DriverException::class); - $test_case = $this->getFixture($session_strategy, array('markTestSkipped')); + $test_case = $this->getFixture(true, $session_strategy); $test_case->setBrowser($browser); - $test_case->shouldReceive('markTestSkipped')->once()->andThrow('\Exception', 'MSG_SKIP'); - $test_case->getSession(); + // On PHPUnit 5.x usage of expectException/expectExceptionMessage results in this test being marked as skipped. + try { + $test_case->getSession(); + } + catch ( \Exception $e ) { + $this->assertInstanceOf(SkippedTestError::class, $e); + $this->assertEquals( + 'The Selenium Server is not active on host {hostname} at port {port}', + $e->getMessage() + ); + } + + if ( !isset($e) ) { + $this->fail('No exception about non-working Selenium server was thrown.'); + } } /** * Returns browser mock. * + * @param integer $times How much times configuration should be read. + * * @return BrowserConfiguration */ - protected function getBrowser() + protected function getBrowserMock($times) { $browser = m::mock(self::BROWSER_CLASS); - $browser->shouldReceive('getSessionStrategy')->once()->andReturnNull(); - $browser->shouldReceive('getSessionStrategyHash')->once()->andReturnNull(); - $browser->shouldReceive('getHost')->andReturnNull(); - $browser->shouldReceive('getPort')->andReturnNull(); + $browser->shouldReceive('getHost')->times($times)->andReturn('{hostname}'); + $browser->shouldReceive('getPort')->times($times)->andReturn('{port}'); return $browser; } /** - * Test description. + * @param string $remote_coverage_script_url Remote coverage script URL. * - * @return void + * @dataProvider getCollectCodeCoverageInformationSuccessDataProvider */ - public function testGetCollectCodeCoverageInformationSuccess() + public function testGetCollectCodeCoverageInformationSuccess($remote_coverage_script_url) { - $test_case = $this->getFixture(); - $test_result = $this->getTestResult($test_case, 0, true); + $test_case = $this->getFixture(false); + + $test_result = new TestResult(); + + // Can't mock due to class being marked as final. + if ( \interface_exists('\PHP_CodeCoverage_Driver') ) { + $code_coverage = new CodeCoverage( + m::mock('\PHP_CodeCoverage_Driver'), + new Filter() + ); + } + else { + $code_coverage = new CodeCoverage(m::mock(Driver::class), new Filter()); + } + + $test_result->setCodeCoverage($code_coverage); $test_case->setTestResultObject($test_result); - $this->assertTrue($test_case->getCollectCodeCoverageInformation()); + if ( $remote_coverage_script_url ) { + $test_case->setRemoteCoverageScriptUrl($remote_coverage_script_url); + $this->assertTrue($test_case->getCollectCodeCoverageInformation()); + } + else { + $this->assertFalse($test_case->getCollectCodeCoverageInformation()); + } } - /** - * Test description. - * - * @return void - * @expectedException \RuntimeException - */ - public function testGetCollectCodeCoverageInformationFailure() + public function getCollectCodeCoverageInformationSuccessDataProvider() { - $this->getFixture()->getCollectCodeCoverageInformation(); + return array( + 'with remote coverage url' => array('http://localhost/'), + 'without remote coverage url' => array(''), + ); } /** * Test description. * * @return void + * @large + * @runInSeparateProcess */ public function testRun() { - /* @var $test_case BrowserTestCase */ + /** @var BrowserTestCase $test_case */ list($test_case,) = $this->prepareForRun(); - $result = $this->getTestResult($test_case, 1); + $result = new TestResult(); $this->assertSame($result, $test_case->run($result)); } @@ -278,35 +351,113 @@ public function testRun() * Test description. * * @return void + * @large + * @runInSeparateProcess */ public function testRunCreateResult() { - /* @var $test_case BrowserTestCase */ + /** @var BrowserTestCase $test_case */ list($test_case,) = $this->prepareForRun(); - $this->assertInstanceOf('\\PHPUnit_Framework_TestResult', $test_case->run()); + $this->assertInstanceOf(TestResult::class, $test_case->run()); } /** * Test description. * * @return void + * @large + * @runInSeparateProcess */ - public function testRunWithCoverage() + public function testRunWithCoverageWithoutRemoteUrl() { - /* @var $test_case BrowserTestCase */ - /* @var $session_strategy ISessionStrategy */ - list($test_case, $session_strategy) = $this->prepareForRun(array('getRemoteCodeCoverageInformation')); + /** @var BrowserTestCase $test_case */ + /** @var ISessionStrategy $session_strategy */ + list($test_case, $session_strategy) = $this->prepareForRun(); $test_case->setName('getTestId'); + $test_case->setRemoteCoverageHelper($this->getRemoteCoverageHelperMock()); + + $result = new TestResult(); + $code_coverage = $this->getCodeCoverageMock(array( + $this->getCoverageFixtureFile() => array( + 7 => -1, // Means line not executed. + 8 => -1, + 9 => -1, + 12 => -1, + 13 => -1, + 14 => -1, + ), + )); + $result->setCodeCoverage($code_coverage); + + $this->assertEmpty($test_case->getTestId()); + + $browser = $test_case->getBrowser(); + $browser->shouldReceive('getBaseUrl')->never(); + + $session_strategy->shouldReceive('session')->never(); + + $test_case->run($result); - $expected_coverage = array('test1' => 'test2'); - $test_case->shouldReceive('getRemoteCodeCoverageInformation')->andReturn($expected_coverage); + if ( \class_exists(ProcessedCodeCoverageData::class) ) { + $actual_coverage = $code_coverage->getData(); + $expected_coverage = new ProcessedCodeCoverageData(); + $expected_coverage->setLineCoverage(array( + $this->getCoverageFixtureFile() => array( + 8 => array(), // First "return null;" statement. + 13 => array(), // Second "return null;" statement. + ), + )); + $this->assertEquals($expected_coverage, $actual_coverage); + } + else { + $actual_coverage = $code_coverage->getData(); + $expected_coverage = array( + $this->getCoverageFixtureFile() => array( + 7 => array(), // Means, that this test hasn't executed a tested code. + 8 => array(), + 9 => array(), + 12 => array(), + 13 => array(), + 14 => array(), + ), + ); + $this->assertEquals($expected_coverage, $actual_coverage); + } + + $this->assertEmpty($test_case->getTestId()); + } - $code_coverage = m::mock('\\PHP_CodeCoverage'); - $code_coverage->shouldReceive('append')->with($expected_coverage, $test_case)->once()->andReturnNull(); + /** + * Test description. + * + * @return void + * @large + * @runInSeparateProcess + */ + public function testRunWithCoverage() + { + $expected_coverage = array( + $this->getCoverageFixtureFile() => array( + 7 => 1, // Means line executed. + 8 => -1, // Means, that this test hasn't executed a tested code. + 9 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + ), + ); + + /** @var BrowserTestCase $test_case */ + /** @var ISessionStrategy $session_strategy */ + list($test_case, $session_strategy) = $this->prepareForRun(); + $test_case->setName('getTestId'); + $test_case->setRemoteCoverageHelper($this->getRemoteCoverageHelperMock($expected_coverage)); + $test_case->setRemoteCoverageScriptUrl('some-url'); - $result = $this->getTestResult($test_case, 1, true); - $result->shouldReceive('getCodeCoverage')->once()->andReturn($code_coverage); + $result = new TestResult(); + $code_coverage = $this->getCodeCoverageMock($expected_coverage); + $result->setCodeCoverage($code_coverage); $test_id = $test_case->getTestId(); $this->assertEmpty($test_id); @@ -314,18 +465,138 @@ public function testRunWithCoverage() $browser = $test_case->getBrowser(); $browser->shouldReceive('getBaseUrl')->once()->andReturn('A'); - $session = m::mock('\\Behat\\Mink\\Session'); - $session->shouldReceive('visit')->with('A')->once()->andReturnNull(); - $session->shouldReceive('setCookie')->with('PHPUNIT_SELENIUM_TEST_ID', null)->once()->andReturnNull(); - $session->shouldReceive('setCookie')->with('PHPUNIT_SELENIUM_TEST_ID', m::not(''))->once()->andReturnNull(); + $session = m::mock(Session::class); + $session->shouldReceive('visit')->with('A')->once(); + $session->shouldReceive('setCookie')->with(RemoteCoverageTool::TEST_ID_VARIABLE, null)->once(); + $session->shouldReceive('setCookie')->with(RemoteCoverageTool::TEST_ID_VARIABLE, m::not(''))->once(); $session_strategy->shouldReceive('session')->once()->andReturn($session); $test_case->run($result); + $covered_by_test = 'tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig::getTestId'; + + if ( \class_exists(ProcessedCodeCoverageData::class) ) { + $expected_coverage = new ProcessedCodeCoverageData(); + $expected_coverage->setLineCoverage(array( + $this->getCoverageFixtureFile() => array( + 8 => array(), // Means, that this test hasn't executed a tested code. + 13 => array($covered_by_test), + ), + )); + $actual_coverage = $code_coverage->getData(); + $this->assertEquals($expected_coverage, $actual_coverage); + } + else { + $actual_coverage = $code_coverage->getData(); + $expected_coverage = array( + $this->getCoverageFixtureFile() => array( + 7 => array($covered_by_test), // Means, covered by this test. + 8 => array(), // Means, that this test hasn't executed a tested code. + 9 => array($covered_by_test), + 12 => array($covered_by_test), + 13 => array($covered_by_test), + 14 => array($covered_by_test), + ), + ); + $this->assertEquals($expected_coverage, $actual_coverage); + } + $this->assertNotEmpty($test_case->getTestId()); } + /** + * Returns the coverage fixture file. + * + * @return string + */ + protected function getCoverageFixtureFile() + { + return \realpath(__DIR__ . '/Fixture/DummyClass.php'); + } + + /** + * Returns remote coverage helper mock. + * + * @param array|null $expected_coverage Expected coverage. + * + * @return RemoteCoverageHelper + */ + protected function getRemoteCoverageHelperMock(array $expected_coverage = null) + { + $remote_coverage_helper = m::mock(RemoteCoverageHelper::class); + + if ( $expected_coverage !== null ) { + if ( \class_exists(RawCodeCoverageData::class) ) { + $remote_coverage_helper + ->shouldReceive('get') + ->with('some-url', 'tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig__getTestId') + ->andReturn(RawCodeCoverageData::fromXdebugWithoutPathCoverage($expected_coverage)); + } + else { + $remote_coverage_helper + ->shouldReceive('get') + ->with('some-url', 'tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig__getTestId') + ->andReturn($expected_coverage); + } + } + + if ( \class_exists(RawCodeCoverageData::class) ) { + $remote_coverage_helper + ->shouldReceive('getEmpty') + ->andReturn(RawCodeCoverageData::fromXdebugWithoutPathCoverage(array())); + } + else { + $remote_coverage_helper + ->shouldReceive('getEmpty') + ->andReturn(array()); + } + + return $remote_coverage_helper; + } + + /** + * Returns code coverage mock. + * + * @param array $expected_coverage Expected coverage. + * + * @return CodeCoverage + */ + protected function getCodeCoverageMock(array $expected_coverage) + { + if ( \interface_exists('\PHP_CodeCoverage_Driver') ) { + $driver = m::mock('\PHP_CodeCoverage_Driver'); + } + else { + $driver = m::mock(Driver::class); + } + + $driver->shouldReceive('start')->once(); + + // Can't assert call count, because expectations are verified prior to coverage being queried. + if ( \class_exists(RawCodeCoverageData::class) ) { + $driver->shouldReceive('stop') + /*->once()*/ + ->andReturn( + RawCodeCoverageData::fromXdebugWithoutPathCoverage($expected_coverage) + ); + } + else { + $driver->shouldReceive('stop')->/*once()->*/andReturn($expected_coverage); + } + + $filter = new Filter(); + + if ( \method_exists($filter, 'addFileToWhitelist') ) { + $filter->addFileToWhitelist($this->getCoverageFixtureFile()); + } + elseif ( \method_exists($filter, 'includeFile') ) { + $filter->includeFile($this->getCoverageFixtureFile()); + } + + return new CodeCoverage($driver, $filter); + } + /** * Test description. * @@ -333,108 +604,99 @@ public function testRunWithCoverage() */ public function testEndOfTestCase() { - /* @var $session_strategy \aik099\PHPUnit\SessionStrategy\ISessionStrategy */ + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - $session_strategy->shouldReceive('endOfTestCase')->with(null)->once()->andReturnNull(); - $test_case = new WithoutBrowserConfig(); + $test_case = new WithoutBrowserConfig('test name'); $test_case->setSessionStrategy($session_strategy); - $this->assertSame($test_case, $test_case->endOfTestCase()); + $session_strategy->shouldReceive('onTestSuiteEnded')->with($test_case)->once(); + + $this->assertSame($test_case, $test_case->onTestSuiteEnded()); } /** * Test description. * * @return void - * @expectedException \Exception - * @expectedExceptionMessage MSG_TEST */ - public function testNotSuccessfulTest() + public function testOnTestFailed() { - /* @var $session_strategy ISessionStrategy */ + $this->expectException('Exception'); + $this->expectExceptionMessage('MSG_TEST'); + + /** @var ISessionStrategy $session_strategy */ $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - $session_strategy->shouldReceive('notSuccessfulTest')->once()->andReturnNull(); - $test_case = $this->getFixture($session_strategy); + $test_case = $this->getFixture(false, $session_strategy); $test_case->setSessionStrategy($session_strategy); + $exception = new \Exception('MSG_TEST'); + $session_strategy->shouldReceive('onTestFailed')->with($test_case, $exception)->once(); + $reflection_method = new \ReflectionMethod($test_case, 'onNotSuccessfulTest'); $reflection_method->setAccessible(true); - $reflection_method->invokeArgs($test_case, array(new \Exception('MSG_TEST'))); + $reflection_method->invokeArgs($test_case, array($exception)); } /** - * Prepares test case to be used by "run" method. - * - * @param array $mock_methods Method names to mock. + * Test description. * - * @return array + * @return void */ - protected function prepareForRun(array $mock_methods = array()) + public function testGetBrowserAliases() { - /* @var $session_strategy ISessionStrategy */ - $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); - $session_strategy->shouldReceive('endOfTest')->once()->andReturnNull(); + $test_case = $this->getFixture(false); - $test_case = $this->getFixture($session_strategy, $mock_methods); - $test_case->setName('testSuccess'); - - $browser = $this->getBrowser(); - $browser->shouldReceive('testSetUpHook')->once()->andReturnNull(); - $browser->shouldReceive('testAfterRunHook')->with($test_case, m::any())->once()->andReturnNull(); - $test_case->setBrowser($browser); - - return array($test_case, $session_strategy); + $this->assertEmpty($test_case->getBrowserAliases(), 'Browser configuration aliases are empty by default'); } /** - * Returns test result. - * - * @param BrowserTestCase $test_case Browser test case. - * @param integer $run_count Test run count. - * @param boolean $collect_coverage Should collect coverage information. + * Prepares test case to be used by "run" method. * - * @return \PHPUnit_Framework_TestResult|\Mockery\Expectation + * @return array */ - protected function getTestResult(BrowserTestCase $test_case, $run_count, $collect_coverage = false) + protected function prepareForRun() { - $result = m::mock('\\PHPUnit_Framework_TestResult'); - $result->shouldReceive('getCollectCodeCoverageInformation')->withNoArgs()->andReturn($collect_coverage); + /** @var ISessionStrategy $session_strategy */ + $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); + + $test_case = $this->getFixture(true, $session_strategy); + $test_case->setName('testSuccess'); + + $session_strategy->shouldReceive('onTestEnded')->with($test_case)->once(); - $result->shouldReceive('run')->with($test_case)->times($run_count)->andReturnUsing(function () use ($test_case) { - $test_case->runBare(); - }); + $browser = $this->getBrowserMock(0); + $browser->shouldReceive('onTestSetup')->with($test_case)->once(); + $browser->shouldReceive('onTestEnded')->with($test_case, m::type(TestResult::class))->once(); - return $result; + $test_case->setBrowser($browser); + + return array($test_case, $session_strategy); } /** * Returns test case fixture. * + * @param boolean $return_strategy Session strategy manager would be asked for a strategy. * @param ISessionStrategy|null $session_strategy Session strategy. - * @param array $mock_methods Method names to mock. * * @return WithoutBrowserConfig */ - protected function getFixture(ISessionStrategy $session_strategy = null, array $mock_methods = array()) + protected function getFixture($return_strategy, ISessionStrategy $session_strategy = null) { if ( !isset($session_strategy) ) { $session_strategy = m::mock(self::SESSION_STRATEGY_INTERFACE); } - /* @var $manager \aik099\PHPUnit\SessionStrategy\SessionStrategyManager */ + /** @var SessionStrategyManager $manager */ $manager = m::mock(self::MANAGER_CLASS); - $manager->shouldReceive('getSessionStrategy')->andReturn($session_strategy); - - if ( $mock_methods ) { - $test_case = m::mock('\\aik099\\PHPUnit\\BrowserTestCase[' . implode(',', $mock_methods) . ']'); - } - else { - $test_case = new WithoutBrowserConfig(); - } + $manager->shouldReceive('getSessionStrategy') + ->times($return_strategy ? 1 : 0) + ->andReturn($session_strategy); + $test_case = new WithoutBrowserConfig('test name'); $test_case->setSessionStrategyManager($manager); return $test_case; diff --git a/tests/aik099/PHPUnit/Common/RemoteCoverageTest.php b/tests/aik099/PHPUnit/Common/RemoteCoverageTest.php deleted file mode 100644 index 6b0d929..0000000 --- a/tests/aik099/PHPUnit/Common/RemoteCoverageTest.php +++ /dev/null @@ -1,70 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace tests\aik099\PHPUnit\Common; - - -use aik099\PHPUnit\Common\RemoteCoverage; -use Mockery as m; - -class RemoteCoverageTest extends \PHPUnit_Framework_TestCase -{ - - /** - * Test description. - * - * @return void - * @expectedException \InvalidArgumentException - */ - public function testIncorrectScriptUrl() - { - new RemoteCoverage('', 'test_id'); - } - - /** - * Test description. - * - * @return void - */ - public function testGetFetchUrl() - { - $remote_coverage = new RemoteCoverage('A', 'B'); - - $this->assertEquals('A?PHPUNIT_SELENIUM_TEST_ID=B', $remote_coverage->getFetchUrl()); - } - - /** - * Test description. - * - * @return void - */ - public function testGet() - { - $remote_coverage = m::mock('\\aik099\\PHPUnit\\Common\\RemoteCoverage[getFetchUrl]', array('url', 'test_id')); - /* @var $remote_coverage RemoteCoverage */ - - $remote_coverage->shouldReceive('getFetchUrl')->andReturn(__DIR__ . '/../Fixture/coverage_data.txt'); - - $content = $remote_coverage->get(); - $class_source_file = realpath(__DIR__ . '/../Fixture/DummyClass.php'); - - $expected = array( - 3 => 1, - 6 => 1, - 7 => -2, - 11 => -1, - 12 => -2, - 14 => 1, - ); - - $this->assertEquals($expected, $content[$class_source_file]); - } - -} diff --git a/tests/aik099/PHPUnit/Fixture/ApiIntegrationFixture.php b/tests/aik099/PHPUnit/Fixture/ApiIntegrationFixture.php new file mode 100644 index 0000000..227a5d0 --- /dev/null +++ b/tests/aik099/PHPUnit/Fixture/ApiIntegrationFixture.php @@ -0,0 +1,252 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Fixture; + + +use aik099\PHPUnit\BrowserConfiguration\ApiBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; + +class ApiIntegrationFixture extends BrowserTestCase +{ + + /** + * Browser list to be used in tests. + * + * @var array + */ + public static $browsers = array( + array('alias' => 'saucelabs'), + array('alias' => 'browserstack'), + ); + + /** + * Record IDs of WebDriver session, that needs to be verified. + * + * @var array + */ + private $_sessionIds = array(); + + /** + * @inheritDoc + * + * @after + */ + protected function tearDownTest() + { + $this->recordSessionId(); + + parent::tearDownTest(); + + $this->verifyRemoteAPICalls(); + } + + /** + * Record WebDriver session ID of the test. + * + * @return void + */ + public function recordSessionId() + { + if ( $this->_getTestSkipMessage() ) { + return; + } + + $session = $this->getSession(false); + + if ( $session === null ) { + $this->markTestSkipped('Unable to connect to SauceLabs/BrowserStack. Please check Internet connection.'); + } + + $this->_sessionIds[$this->getName(false)] = $session->getDriver()->getWebDriverSessionId(); + } + + /** + * Verify how the API calls were made. + * + * @return void + */ + public function verifyRemoteAPICalls() + { + $test_name = $this->getName(false); + + if ( !isset($this->_sessionIds[$test_name]) ) { + return; + } + + $browser = $this->getBrowser(); + + if ( $browser instanceof ApiBrowserConfiguration ) { + $api_client = $browser->getAPIClient(); + $session_info = $api_client->getInfo($this->_sessionIds[$test_name]); + + if ( $browser instanceof SauceLabsBrowserConfiguration ) { + $this->assertEquals( + get_class($this) . '::' . $test_name, + $session_info['name'], + 'SauceLabs remote session name matches test name' + ); + + $passed_mapping = array( + 'testSuccess' => true, + 'testFailure' => false, + ); + + $this->assertSame( + $passed_mapping[$test_name], + $session_info['passed'], + 'SauceLabs test status set via API' + ); + + $custom_data_mapping = array( + 'testSuccess' => null, + 'testFailure' => array( + 'status_message' => "This test is expected to fail.\nFailed asserting that false is true.", + ), + ); + + $this->assertSame( + $custom_data_mapping[$test_name], + $session_info['custom-data'], + 'SauceLabs test status message set via API' + ); + } + elseif ( $browser instanceof BrowserStackBrowserConfiguration ) { + $this->assertEquals( + \str_replace('\\', '-', get_class($this) . '::' . $test_name), + $session_info['name'], + 'BrowserStack remote session name matches test name' + ); + + $passed_mapping = array( + 'testSuccess' => 'passed', + 'testFailure' => 'failed', + ); + + $this->assertSame( + $passed_mapping[$test_name], + $session_info['status'], + 'BrowserStack test status set via API' + ); + + $reason_mapping = array( + 'testSuccess' => '', + 'testFailure' => "This test is expected to fail.\nFailed asserting that false is true.", + ); + + $this->assertSame( + $reason_mapping[$test_name], + $session_info['reason'], + 'BrowserStack test status message set via API' + ); + } + } + } + + /** + * Test description. + * + * @param boolean $data Data. + * + * @return void + * @dataProvider successDataProvider + */ + public function testSuccess($data) + { + $skip_message = $this->_getTestSkipMessage(); + + if ( $skip_message ) { + $this->markTestSkipped($skip_message); + } + + $session = $this->getSession(); + $session->visit('http://www.google.com'); + + $this->assertTrue(true); + } + + public static function successDataProvider() + { + return array( + 'true' => array(true), + 'false' => array(false), + ); + } + + /** + * Test description. + * + * @return void + */ + public function testFailure() + { + $skip_message = $this->_getTestSkipMessage(); + + if ( $skip_message ) { + $this->markTestSkipped($skip_message); + } + + $session = $this->getSession(); + $session->visit('http://www.google.com'); + + $this->assertTrue(false, 'This test is expected to fail.'); + } + + /** + * Gets browser configuration aliases. + * + * Allows to decouple actual test server connection details from test cases. + * + * @return array + */ + public function getBrowserAliases() + { + return array( + 'saucelabs' => array( + 'type' => 'saucelabs', + 'apiUsername' => getenv('SAUCE_USERNAME'), + 'apiKey' => getenv('SAUCE_ACCESS_KEY'), + + 'browserName' => 'chrome', + 'desiredCapabilities' => array('build' => BUILD_NAME, 'version' => 120), + 'baseUrl' => 'http://www.google.com', + ), + 'browserstack' => array( + 'type' => 'browserstack', + 'api_username' => getenv('BS_USERNAME'), + 'api_key' => getenv('BS_ACCESS_KEY'), + + 'browserName' => 'chrome', + 'desiredCapabilities' => array('build' => BUILD_NAME, 'browser_version' => 120), + ), + ); + } + + private function _getTestSkipMessage() + { + $browser = $this->getBrowser(); + + if ( $browser->getType() == 'saucelabs' ) { + if ( !getenv('SAUCE_USERNAME') || !getenv('SAUCE_ACCESS_KEY') ) { + return 'SauceLabs integration is not configured'; + } + } + elseif ( $browser->getType() == 'browserstack' ) { + if ( !getenv('BS_USERNAME') || !getenv('BS_ACCESS_KEY') ) { + return 'BrowserStack integration is not configured'; + } + } + + return ''; + } + +} diff --git a/tests/aik099/PHPUnit/Fixture/SetupFixture.php b/tests/aik099/PHPUnit/Fixture/SetupFixture.php new file mode 100644 index 0000000..b88a393 --- /dev/null +++ b/tests/aik099/PHPUnit/Fixture/SetupFixture.php @@ -0,0 +1,125 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Fixture; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\BrowserConfiguration\ApiBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use Behat\Mink\Session; +use Mockery as m; +use tests\aik099\PHPUnit\TVerifyTestExpectations; +use aik099\PHPUnit\APIClient\IAPIClient; +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; +use Behat\Mink\Driver\Selenium2Driver; + +class SetupFixture extends BrowserTestCase +{ + + use TVerifyTestExpectations; + + /** + * @before + */ + protected function setUpTest() + { + $api_client = m::mock(IAPIClient::class); + $api_client->shouldReceive('updateStatus')->withAnyArgs()->once(); + + /** @var APIClientFactory $api_client_factory */ + $api_client_factory = m::mock(APIClientFactory::class); + + $browser = new SauceLabsBrowserConfiguration($this->createDriverFactoryRegistry(), $api_client_factory); + + $desired_capabilities = $browser->getDesiredCapabilities(); + $desired_capabilities[ApiBrowserConfiguration::NAME_CAPABILITY] = 'something'; + $browser->setDesiredCapabilities($desired_capabilities); + + $api_client_factory->shouldReceive('getAPIClient')->with($browser)->once()->andReturn($api_client); + + $browser_config = array('apiUsername' => 'a', 'apiKey' => 'b'); + + $factory = m::mock(IBrowserConfigurationFactory::class); + + $factory->shouldReceive('createBrowserConfiguration') + ->with($browser_config, $this) + ->once() + ->andReturn($browser); + $this->setBrowserConfigurationFactory($factory); + + $this->setBrowserFromConfiguration($browser_config); + + parent::setUpTest(); + } + + /** + * Creates driver factory registry. + * + * @return DriverFactoryRegistry + */ + protected function createDriverFactoryRegistry() + { + $registry = m::mock(DriverFactoryRegistry::class); + + $driver_factory = m::mock(IMinkDriverFactory::class); + $driver_factory->shouldReceive('getDriverDefaults')->andReturn(array()); + + $registry + ->shouldReceive('get') + ->with('selenium2') + ->andReturn($driver_factory); + + return $registry; + } + + /** + * Test description. + * + * @return void + */ + public function testEvents() + { + $driver = m::mock(Selenium2Driver::class); + $driver->shouldReceive('getWebDriverSessionId')->once()->andReturn('SID'); + + $session = m::mock(Session::class); + + // For ApiBrowserConfiguration::onTestEnded. + $session->shouldReceive('getDriver')->once()->andReturn($driver); + + $session->shouldReceive('stop')->once(); + $session->shouldReceive('isStarted')->andReturn(true); + + $this->_setSession($session); + + // For ApiBrowserConfiguration::onTestSetup. + $desired_capabilities = $this->getBrowser()->getDesiredCapabilities(); + $this->assertArrayHasKey(ApiBrowserConfiguration::NAME_CAPABILITY, $desired_capabilities); + } + + /** + * Replaces session with a given one. + * + * @param Session $session Session. + * + * @return void + */ + private function _setSession(Session $session) + { + $property = new \ReflectionProperty(BrowserTestCase::class, '_session'); + $property->setAccessible(true); + $property->setValue($this, $session); + } + +} diff --git a/tests/aik099/PHPUnit/Fixture/WithoutBrowserConfig.php b/tests/aik099/PHPUnit/Fixture/WithoutBrowserConfig.php index 62770e6..fe8ea97 100644 --- a/tests/aik099/PHPUnit/Fixture/WithoutBrowserConfig.php +++ b/tests/aik099/PHPUnit/Fixture/WithoutBrowserConfig.php @@ -23,7 +23,7 @@ class WithoutBrowserConfig extends BrowserTestCase */ public function testSuccess() { - + echo ''; } /** diff --git a/tests/aik099/PHPUnit/Fixture/coverage_data.txt b/tests/aik099/PHPUnit/Fixture/coverage_data.txt index ec19b7d..ce29cb2 100644 --- a/tests/aik099/PHPUnit/Fixture/coverage_data.txt +++ b/tests/aik099/PHPUnit/Fixture/coverage_data.txt @@ -1 +1 @@ -a:1:{s:75:"/home/giorgio/code/phpunit-mink/tests/aik099/PHPUnit/Fixture/DummyClass.php";a:2:{s:3:"md5";s:32:"290172d322b7de8b5898e54deadbb33e";s:8:"coverage";a:6:{i:3;i:1;i:6;i:1;i:7;i:-2;i:11;i:-1;i:12;i:-2;i:14;i:1;}}} +a:1:{s:75:"/home/giorgio/code/phpunit-mink/tests/aik099/PHPUnit/Fixture/DummyClass.php";a:2:{s:3:"md5";s:32:"290172d322b7de8b5898e54deadbb33e";s:8:"coverage";a:6:{i:3;i:1;i:6;i:1;i:7;i:-2;i:11;i:-1;i:12;i:-2;i:14;i:1;}}} \ No newline at end of file diff --git a/tests/aik099/PHPUnit/Integration/ApiIntegrationTest.php b/tests/aik099/PHPUnit/Integration/ApiIntegrationTest.php new file mode 100644 index 0000000..93f827c --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/ApiIntegrationTest.php @@ -0,0 +1,49 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; +use tests\aik099\PHPUnit\AbstractTestCase; +use tests\aik099\PHPUnit\Fixture\ApiIntegrationFixture; + +class ApiIntegrationTest extends AbstractTestCase +{ + + /** + * @before + */ + protected function setUpTest() + { + // Define the constant because this test is running PHPUnit testcases manually. + if ( $this->isInIsolation() ) { + define('PHPUNIT_TESTSUITE', true); + } + } + + /** + * Test description. + * + * @return void + * @large + * @runInSeparateProcess + */ + public function testAPICalls() + { + $result = new TestResult(); + + $suite = ApiIntegrationFixture::suite(ApiIntegrationFixture::class); + $suite->run($result); + + $this->assertTrue(true); + } + +} diff --git a/tests/aik099/PHPUnit/Integration/BrowserStackAwareTestCase.php b/tests/aik099/PHPUnit/Integration/BrowserStackAwareTestCase.php new file mode 100644 index 0000000..7bec46a --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/BrowserStackAwareTestCase.php @@ -0,0 +1,90 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use aik099\PHPUnit\BrowserTestCase; +use Behat\Mink\Exception\DriverException; +use Behat\Mink\Session; + +abstract class BrowserStackAwareTestCase extends BrowserTestCase +{ + + /** + * Browser list to be used in tests. + * + * @var array + */ + public static $browsers = array( + array( + 'alias' => 'default', + ), + ); + + /** + * Visit specified URL and automatically start session if not already running. + * + * @param Session $session Session. + * @param string $url Url of the page. + * + * @return void + * @throws DriverException + */ + protected function openPageWithBackoff(Session $session, $url) + { + try { + $session->visit($url); + } + catch ( DriverException $e ) { + if ( strpos($e->getMessage(), '[BROWSERSTACK_QUEUE_SIZE_EXCEEDED]') !== false ) { + sleep(30); + $this->openPageWithBackoff($session, $url); + + return; + } + + throw $e; + } + } + + /** + * @before + */ + protected function setUpTest() + { + if ( !getenv('BS_USERNAME') || !getenv('BS_ACCESS_KEY') ) { + $this->markTestSkipped('BrowserStack integration is not configured'); + } + } + + /** + * Gets browser configuration aliases. + * + * Allows to decouple actual test server connection details from test cases. + * + * @return array + */ + public function getBrowserAliases() + { + return array( + 'default' => array( + 'type' => 'browserstack', + 'api_username' => getenv('BS_USERNAME'), + 'api_key' => getenv('BS_ACCESS_KEY'), + + 'browserName' => 'chrome', + 'desiredCapabilities' => array('browser_version' => 110), + 'baseUrl' => 'http://www.google.com', + ), + ); + } + +} diff --git a/tests/aik099/PHPUnit/Integration/DIContainerTest.php b/tests/aik099/PHPUnit/Integration/DIContainerTest.php new file mode 100644 index 0000000..c798ead --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/DIContainerTest.php @@ -0,0 +1,150 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use aik099\PHPUnit\APIClient\APIClientFactory; +use aik099\PHPUnit\Application; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfigurationFactory; +use aik099\PHPUnit\BrowserConfiguration\BrowserStackBrowserConfiguration; +use aik099\PHPUnit\BrowserConfiguration\SauceLabsBrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\DIContainer; +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use aik099\PHPUnit\MinkDriver\GoutteDriverFactory; +use aik099\PHPUnit\MinkDriver\SahiDriverFactory; +use aik099\PHPUnit\MinkDriver\Selenium2DriverFactory; +use aik099\PHPUnit\MinkDriver\WebdriverClassicFactory; +use aik099\PHPUnit\MinkDriver\ZombieDriverFactory; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\RemoteCoverage\RemoteUrl; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use aik099\PHPUnit\Session\IsolatedSessionStrategy; +use aik099\PHPUnit\Session\SessionStrategyFactory; +use aik099\PHPUnit\Session\SessionStrategyManager; +use aik099\PHPUnit\Session\SharedSessionStrategy; +use aik099\PHPUnit\TestSuite\BrowserTestSuite; +use aik099\PHPUnit\TestSuite\RegularTestSuite; +use aik099\PHPUnit\TestSuite\TestSuiteFactory; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; + +class DIContainerTest extends AbstractTestCase +{ + + /** + * Container. + * + * @var DIContainer + */ + private $_container; + + /** + * Creates container for testing. + * + * @before + */ + protected function setUpTest() + { + $this->_container = new DIContainer(); + $this->_container->setApplication(new Application()); + } + + /** + * Test description. + * + * @param string $service_id Service ID. + * @param string $class_name Class name. + * + * @return void + * @dataProvider serviceDefinitionsDataProvider + */ + public function testServiceDefinitions($service_id, $class_name) + { + $this->assertInstanceOf($class_name, $this->_container[$service_id]); + } + + /** + * Provides expectations for service definitions. + * + * @return array + */ + public static function serviceDefinitionsDataProvider() + { + return array( + array('application', Application::class), + array('session_strategy_factory', SessionStrategyFactory::class), + array('session_strategy_manager', SessionStrategyManager::class), + array('remote_url', RemoteUrl::class), + array('remote_coverage_helper', RemoteCoverageHelper::class), + array('test_suite_factory', TestSuiteFactory::class), + array('regular_test_suite', RegularTestSuite::class), + array('browser_test_suite', BrowserTestSuite::class), + array('api_client_factory', APIClientFactory::class), + array('browser_configuration_factory', BrowserConfigurationFactory::class), + array('driver_factory_registry', DriverFactoryRegistry::class), + ); + } + + public function testSessionStrategyFactory() + { + /** @var SessionStrategyFactory $session_strategy_factory */ + $session_strategy_factory = $this->_container['session_strategy_factory']; + + $this->assertInstanceOf( + IsolatedSessionStrategy::class, + $session_strategy_factory->createStrategy(ISessionStrategyFactory::TYPE_ISOLATED) + ); + + $this->assertInstanceOf( + SharedSessionStrategy::class, + $session_strategy_factory->createStrategy(ISessionStrategyFactory::TYPE_SHARED) + ); + } + + public function testDriverFactoryRegistry() + { + /** @var DriverFactoryRegistry $driver_factory_registry */ + $driver_factory_registry = $this->_container['driver_factory_registry']; + + $this->assertInstanceOf(Selenium2DriverFactory::class, $driver_factory_registry->get('selenium2')); + $this->assertInstanceOf(WebdriverClassicFactory::class, $driver_factory_registry->get('webdriver-classic')); + $this->assertInstanceOf(SahiDriverFactory::class, $driver_factory_registry->get('sahi')); + $this->assertInstanceOf(GoutteDriverFactory::class, $driver_factory_registry->get('goutte')); + $this->assertInstanceOf(ZombieDriverFactory::class, $driver_factory_registry->get('zombie')); + } + + public function testBrowserConfigurationFactory() + { + $test_case = m::mock(BrowserTestCase::class); + $test_case->shouldReceive('getBrowserAliases')->andReturn(array()); + + /** @var BrowserConfigurationFactory $browser_configuration_factory */ + $browser_configuration_factory = $this->_container['browser_configuration_factory']; + + $this->assertInstanceOf( + BrowserConfiguration::class, + $browser_configuration_factory->createBrowserConfiguration(array('type' => 'default'), $test_case) + ); + + $this->assertInstanceOf( + BrowserStackBrowserConfiguration::class, + $browser_configuration_factory->createBrowserConfiguration(array('type' => 'browserstack'), $test_case) + ); + + $this->assertInstanceOf( + SauceLabsBrowserConfiguration::class, + $browser_configuration_factory->createBrowserConfiguration(array('type' => 'saucelabs'), $test_case) + ); + } + +} diff --git a/tests/aik099/PHPUnit/Integration/DataProviderTest.php b/tests/aik099/PHPUnit/Integration/DataProviderTest.php new file mode 100644 index 0000000..c42068e --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/DataProviderTest.php @@ -0,0 +1,57 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +class DataProviderTest extends BrowserStackAwareTestCase +{ + + /** + * Browser list to be used in tests. + * + * @var array + */ + public static $browsers = array( + array( + 'alias' => 'default', + 'desiredCapabilities' => array('build' => BUILD_NAME, 'name' => 'DataProviderTest'), + ), + ); + + public static function sampleDataProvider() + { + return array( + array('case1'), + array('case2'), + ); + } + + /** + * @dataProvider sampleDataProvider + */ + public function testDataProvider($case) + { + $this->customMethod(); + + if ( $case === 'case1' || $case === 'case2' ) { + $this->assertTrue(true); + } + else { + $this->fail('Unknown $case: ' . $case); + } + } + + protected function customMethod() + { + return 5; + } + +} diff --git a/tests/aik099/PHPUnit/Integration/EventDispatchingTest.php b/tests/aik099/PHPUnit/Integration/EventDispatchingTest.php new file mode 100644 index 0000000..dbf9a31 --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/EventDispatchingTest.php @@ -0,0 +1,100 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; +use PHPUnit\Framework\TestFailure; +use tests\aik099\PHPUnit\AbstractTestCase; +use tests\aik099\PHPUnit\Fixture\SetupFixture; + +class EventDispatchingTest extends AbstractTestCase +{ + + /** + * @before + */ + protected function setUpTest() + { + // Define the constant because this test is running PHPUnit testcases manually. + if ( $this->isInIsolation() ) { + define('PHPUNIT_TESTSUITE', true); + } + } + + /** + * Test description. + * + * @return void + * @large + * @runInSeparateProcess + */ + public function testSetupEvent() + { + /* + * BrowserTestCase::TEST_SETUP_EVENT + * - SauceLabsBrowserConfiguration::onTestSetup (called, verified) + * + * BrowserTestCase::TEST_ENDED_EVENT + * - IsolatedSessionStrategy::onTestEnd (called, verified) + * - SauceLabsBrowserConfiguration::onTestEnded (called, verified) + * + * BrowserTestCase::TEST_SUITE_ENDED_EVENT + * - SharedSessionStrategy::onTestSuiteEnd + * + * BrowserTestCase::TEST_FAILED_EVENT + * - SharedSessionStrategy::onTestFailed + */ + + $result = new TestResult(); + + $suite = SetupFixture::suite(SetupFixture::class); + $suite->run($result); + + $error_msgs = array(); + + if ( $result->errorCount() > 0 ) { + foreach ( $result->errors() as $error ) { + $error_msgs[] = $this->prepareErrorMsg($error); + } + } + + if ( $result->failureCount() > 0 ) { + foreach ( $result->failures() as $failure ) { + $error_msgs[] = $this->prepareErrorMsg($failure); + } + } + + if ( $error_msgs ) { + $this->fail( + 'The "SetupFixture" tests failed:' . \PHP_EOL . \PHP_EOL . implode(\PHP_EOL . ' * ', $error_msgs) + ); + } + + $this->assertTrue(true); + } + + /** + * Prepares an error msg. + * + * @param TestFailure $test_failure Exception. + * + * @return string + */ + protected function prepareErrorMsg(TestFailure $test_failure) + { + $ret = $test_failure->toString() . \PHP_EOL; + $ret .= 'Trace:' . \PHP_EOL . $test_failure->thrownException()->getTraceAsString(); + + return $ret; + } + +} diff --git a/tests/aik099/PHPUnit/Integration/IsolatedSessionStrategyTest.php b/tests/aik099/PHPUnit/Integration/IsolatedSessionStrategyTest.php new file mode 100644 index 0000000..ffa4597 --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/IsolatedSessionStrategyTest.php @@ -0,0 +1,57 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; + +class IsolatedSessionStrategyTest extends BrowserStackAwareTestCase +{ + + use AssertStringContains; + + /** + * Browser list to be used in tests. + * + * @var array + */ + public static $browsers = array( + array( + 'alias' => 'default', + 'sessionStrategy' => 'isolated', + 'desiredCapabilities' => array('build' => BUILD_NAME, 'name' => 'IsolatedSessionStrategyTest'), + ), + ); + + /** + * @large + */ + public function testOne() + { + $session = $this->getSession(); + $this->openPageWithBackoff($session, 'https://www.google.com'); + + $this->assertTrue(true); + } + + /** + * @large + * @depends testOne + */ + public function testTwo() + { + $session = $this->getSession(); + $url = $session->isStarted() ? $session->getCurrentUrl() : ''; + + $this->assertStringNotContainsString('https://www.google.com', $url); + } + +} diff --git a/tests/aik099/PHPUnit/Integration/SharedSessionStrategyTest.php b/tests/aik099/PHPUnit/Integration/SharedSessionStrategyTest.php new file mode 100644 index 0000000..ee4cb62 --- /dev/null +++ b/tests/aik099/PHPUnit/Integration/SharedSessionStrategyTest.php @@ -0,0 +1,80 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Integration; + + +use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; + +class SharedSessionStrategyTest extends BrowserStackAwareTestCase +{ + + use AssertStringContains; + + /** + * Browser list to be used in tests. + * + * @var array + */ + public static $browsers = array( + array( + 'alias' => 'default', + 'sessionStrategy' => 'shared', + 'desiredCapabilities' => array('build' => BUILD_NAME, 'name' => 'SharedSessionStrategyTest'), + ), + ); + + /** + * @large + */ + public function testOpensPage() + { + $session = $this->getSession(); + $this->openPageWithBackoff($session, 'https://www.google.com'); + + $this->assertTrue(true); + } + + /** + * @large + * @depends testOpensPage + */ + public function testUsesOpenedPage() + { + $session = $this->getSession(); + $url = $session->getCurrentUrl(); + + $this->assertStringContainsString('https://www.google.com', $url); + } + + public function testOpensPopups() + { + $session = $this->getSession(); + $this->openPageWithBackoff($session, 'https://the-internet.herokuapp.com/windows'); + + $page = $session->getPage(); + $page->clickLink('Click Here'); + $page->clickLink('Click Here'); + + $this->assertCount(3, $session->getWindowNames()); // Main window + 2 popups. + } + + /** + * @depends testOpensPopups + */ + public function testNoPopupsBeforeTest() + { + $session = $this->getSession(); + $this->assertEquals('https://the-internet.herokuapp.com/windows', $session->getCurrentUrl()); + + $this->assertCount(1, $session->getWindowNames()); // Main window. + } + +} diff --git a/tests/aik099/PHPUnit/SuiteBuildingTest.php b/tests/aik099/PHPUnit/Integration/SuiteBuildingTest.php similarity index 50% rename from tests/aik099/PHPUnit/SuiteBuildingTest.php rename to tests/aik099/PHPUnit/Integration/SuiteBuildingTest.php index be5fa68..29b3792 100644 --- a/tests/aik099/PHPUnit/SuiteBuildingTest.php +++ b/tests/aik099/PHPUnit/Integration/SuiteBuildingTest.php @@ -8,25 +8,30 @@ * @link https://github.com/aik099/phpunit-mink */ -namespace tests\aik099\PHPUnit; +namespace tests\aik099\PHPUnit\Integration; -use aik099\PHPUnit\BrowserSuite; -use aik099\PHPUnit\TestSuite; +use aik099\PHPUnit\BrowserTestCase; +use aik099\PHPUnit\TestSuite\BrowserTestSuite; +use aik099\PHPUnit\TestSuite\RegularTestSuite; +use ConsoleHelpers\PHPUnitCompat\Framework\TestResult; +use Mockery as m; +use PHPUnit\Runner\Version; +use tests\aik099\PHPUnit\AbstractTestCase; use tests\aik099\PHPUnit\Fixture\WithBrowserConfig; use tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig; -use Mockery as m; +use aik099\PHPUnit\Session\ISessionStrategy; -class SuiteBuildingTest extends \PHPUnit_Framework_TestCase +class SuiteBuildingTest extends AbstractTestCase { - const SUITE_CLASS = '\\aik099\\PHPUnit\\TestSuite'; + const SUITE_CLASS = RegularTestSuite::class; - const BROWSER_SUITE_CLASS = '\\aik099\\PHPUnit\\BrowserSuite'; + const BROWSER_SUITE_CLASS = BrowserTestSuite::class; - const TEST_CASE_WITH_CONFIG = '\\tests\\aik099\\PHPUnit\\Fixture\\WithBrowserConfig'; + const TEST_CASE_WITH_CONFIG = WithBrowserConfig::class; - const TEST_CASE_WITHOUT_CONFIG = '\\tests\\aik099\\PHPUnit\\Fixture\\WithoutBrowserConfig'; + const TEST_CASE_WITHOUT_CONFIG = WithoutBrowserConfig::class; /** * Tests, that suite is built correctly in case, when static $browsers array is filled-in in test case class. @@ -39,13 +44,20 @@ public function testWithBrowserConfiguration() $this->assertInstanceOf(self::SUITE_CLASS, $suite); - $tests = $suite->tests(); - /* @var $tests BrowserSuite[] */ + /** @var BrowserTestSuite[] $test_suites */ + $test_suites = $suite->tests(); + + $this->checkArray($test_suites, 2, self::BROWSER_SUITE_CLASS); - $this->checkArray($tests, 2, self::BROWSER_SUITE_CLASS); + $property = new \ReflectionProperty(BrowserTestCase::class, '_sessionStrategy'); + $property->setAccessible(true); - foreach ( $tests as $test ) { - $this->checkArray($test->tests(), 2, self::TEST_CASE_WITH_CONFIG); + foreach ( $test_suites as $test_suite ) { + /** @var BrowserTestCase[] $suite_tests */ + $suite_tests = $test_suite->tests(); + $this->checkArray($suite_tests, 2, self::TEST_CASE_WITH_CONFIG); + + $this->assertInstanceOf(ISessionStrategy::class, $property->getValue($suite_tests[0])); } } @@ -73,15 +85,12 @@ public function testSuiteTearDown() $sub_browser_suite = $this->createTestSuite(self::BROWSER_SUITE_CLASS); $sub_test_suite = $this->createTestSuite(self::SUITE_CLASS); - $suite = new TestSuite(); + $suite = new RegularTestSuite(); $suite->setName(self::TEST_CASE_WITH_CONFIG); $suite->addTest($sub_browser_suite); $suite->addTest($sub_test_suite); - $result = m::mock('\\PHPUnit_Framework_TestResult'); - $result->shouldReceive('startTestSuite')->andReturnNull(); - $result->shouldReceive('shouldStop')->andReturn(false); - $result->shouldReceive('endTestSuite')->andReturnNull(); + $result = new TestResult(); // Can't mock, because it's a final class. $this->assertSame($result, $suite->run($result)); } @@ -91,22 +100,51 @@ public function testSuiteTearDown() * * @param string $class_name Class name. * - * @return TestSuite + * @return RegularTestSuite */ protected function createTestSuite($class_name) { $suite = m::mock($class_name); $suite->shouldReceive('getGroups')->once()->andReturn(array()); - $suite->shouldReceive('setBackupGlobals')->andReturnNull(); - $suite->shouldReceive('setBackupStaticAttributes')->andReturnNull(); + $suite->shouldReceive('setDisallowChangesToGlobalState'); // Since PHPUnit 4.6.0. + $suite->shouldReceive('setBackupGlobals'); + $suite->shouldReceive('setBackupStaticAttributes'); + $suite->shouldReceive('setRunTestInSeparateProcess'); + + $suite->shouldReceive('run')->once()->andReturnUsing(function ($result = null) { + return $result; + }); + $suite->shouldReceive('onTestSuiteEnded')->once(); - $suite->shouldReceive('run')->once()->andReturnNull(); - $suite->shouldReceive('endOfTestCase')->once()->andReturnNull(); + $phpunit_version = $this->getPhpUnitVersion(); + + // Pretend, that mocked test suite has 1 test inside it. + if ( version_compare($phpunit_version, '4.0.0', '>=') ) { + $suite->shouldReceive('count')->once()->andReturn(1); + } + + if ( version_compare($phpunit_version, '5.0.0', '>=') ) { + $suite->shouldReceive('setBeStrictAboutChangesToGlobalState'); + } return $suite; } + /** + * Returns PHPUnit version. + * + * @return string + */ + protected function getPhpUnitVersion() + { + if ( \class_exists(Version::class) ) { + return Version::id(); + } + + return \PHPUnit_Runner_Version::id(); + } + /** * Checks, that array has a desired structure & contents. * diff --git a/tests/aik099/PHPUnit/MinkDriver/DriverFactoryRegistryTest.php b/tests/aik099/PHPUnit/MinkDriver/DriverFactoryRegistryTest.php new file mode 100644 index 0000000..97ad462 --- /dev/null +++ b/tests/aik099/PHPUnit/MinkDriver/DriverFactoryRegistryTest.php @@ -0,0 +1,89 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace tests\aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\MinkDriver\DriverFactoryRegistry; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; + +class DriverFactoryRegistryTest extends AbstractTestCase +{ + + use ExpectException; + + /** + * Driver factory registry. + * + * @var DriverFactoryRegistry|m\MockInterface + */ + private $_driverFactoryRegistry; + + /** + * @before + */ + public function setUpTest() + { + $this->_driverFactoryRegistry = new DriverFactoryRegistry(); + } + + public function testAddingAndGetting() + { + $factory = m::mock(IMinkDriverFactory::class); + $factory->shouldReceive('getDriverName')->andReturn('test'); + + $this->_driverFactoryRegistry->add($factory); + + $this->assertSame($factory, $this->_driverFactoryRegistry->get('test')); + } + + public function testAddingExisting() + { + $this->expectException('LogicException'); + $this->expectExceptionMessage('Driver factory for "test" driver is already registered.'); + + $factory = m::mock(IMinkDriverFactory::class); + $factory->shouldReceive('getDriverName')->andReturn('test'); + + $this->_driverFactoryRegistry->add($factory); + $this->_driverFactoryRegistry->add($factory); + } + + public function testGettingNonExistingWithoutAlternatives() + { + $this->expectException('OutOfBoundsException'); + $this->expectExceptionMessage('The "test" driver is unknown.'); + + $this->_driverFactoryRegistry->get('test'); + } + + public function testGettingNonExistingWithAlternatives() + { + $this->expectException('OutOfBoundsException'); + $this->expectExceptionMessage( + 'The "test" driver is unknown. Please instead use any of these supported drivers: "driver1", "driver2".' + ); + + $factory1 = m::mock(IMinkDriverFactory::class); + $factory1->shouldReceive('getDriverName')->once()->andReturn('driver1'); + $this->_driverFactoryRegistry->add($factory1); + + $factory2 = m::mock(IMinkDriverFactory::class); + $factory2->shouldReceive('getDriverName')->once()->andReturn('driver2'); + $this->_driverFactoryRegistry->add($factory2); + + $this->_driverFactoryRegistry->get('test'); + } + +} diff --git a/tests/aik099/PHPUnit/MinkDriver/DriverFactoryTest.php b/tests/aik099/PHPUnit/MinkDriver/DriverFactoryTest.php new file mode 100644 index 0000000..2dda560 --- /dev/null +++ b/tests/aik099/PHPUnit/MinkDriver/DriverFactoryTest.php @@ -0,0 +1,112 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + + +namespace tests\aik099\PHPUnit\MinkDriver; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\DIContainer; +use aik099\PHPUnit\MinkDriver\IMinkDriverFactory; +use aik099\PHPUnit\MinkDriver\WebdriverClassicFactory; +use tests\aik099\PHPUnit\AbstractTestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use aik099\PHPUnit\MinkDriver\GoutteDriverFactory; +use aik099\PHPUnit\MinkDriver\SahiDriverFactory; +use aik099\PHPUnit\MinkDriver\Selenium2DriverFactory; +use aik099\PHPUnit\MinkDriver\ZombieDriverFactory; + +class DriverFactoryTest extends AbstractTestCase +{ + + use ExpectException; + + /** + * @dataProvider driverDataProvider + */ + public function testProperDriverReturned($driver_class, $factory_class) + { + if ( !class_exists($driver_class) ) { + $this->markTestSkipped(sprintf('Mink driver "%s" is not installed.', $driver_class)); + } + + /** @var IMinkDriverFactory $factory */ + $factory = new $factory_class(); + + $this->assertInstanceOf($driver_class, $factory->createDriver($this->createBrowserConfiguration($factory))); + } + + /** + * @dataProvider driverDataProvider + */ + public function testMinkDriverMissingError($driver_class, $factory_class) + { + if ( class_exists($driver_class) ) { + $this->markTestSkipped(sprintf('Mink driver "%s" is installed.', $driver_class)); + } + + /** @var IMinkDriverFactory $factory */ + $factory = new $factory_class(); + + $this->expectException('RuntimeException'); + $this->expectExceptionMessage( + sprintf( + 'The "%s" driver package is not installed. Please follow installation instructions at %s.', + $factory->getDriverName(), + $factory->getDriverPackageUrl() + ) + ); + $factory->createDriver($this->createBrowserConfiguration($factory)); + } + + /** + * Creates the browser configuration. + * + * @param IMinkDriverFactory $factory Driver factory. + * + * @return BrowserConfiguration + */ + protected function createBrowserConfiguration(IMinkDriverFactory $factory) + { + $di = new DIContainer(); + + $browser_configuration = new BrowserConfiguration($di['driver_factory_registry']); + $browser_configuration->setDriver($factory->getDriverName()); + + return $browser_configuration; + } + + public static function driverDataProvider() + { + return array( + 'goutte' => array( + '\Behat\Mink\Driver\GoutteDriver', + GoutteDriverFactory::class, + ), + 'sahi' => array( + '\Behat\Mink\Driver\SahiDriver', + SahiDriverFactory::class, + ), + 'selenium2' => array( + '\Behat\Mink\Driver\Selenium2Driver', + Selenium2DriverFactory::class, + ), + 'webdriver-classic' => array( + '\Mink\WebdriverClassicDriver\WebdriverClassicDriver', + WebdriverClassicFactory::class, + ), + 'zombie' => array( + '\Behat\Mink\Driver\ZombieDriver', + ZombieDriverFactory::class, + ), + ); + } + +} diff --git a/tests/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelperTest.php b/tests/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelperTest.php new file mode 100644 index 0000000..952f06c --- /dev/null +++ b/tests/aik099/PHPUnit/RemoteCoverage/RemoteCoverageHelperTest.php @@ -0,0 +1,177 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\RemoteCoverage; + + +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\RemoteCoverage\RemoteUrl; +use Mockery as m; +use Mockery\MockInterface; +use tests\aik099\PHPUnit\AbstractTestCase; +use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +class RemoteCoverageHelperTest extends AbstractTestCase +{ + + use ExpectException, AssertIsType; + + /** + * Remote URL. + * + * @var RemoteUrl|MockInterface + */ + private $_remoteUrl; + + /** + * Remote coverage helper. + * + * @var RemoteCoverageHelper + */ + private $_remoteCoverageHelper; + + /** + * Prepares test. + * + * @before + */ + protected function setUpTest() + { + $this->_remoteUrl = m::mock(RemoteUrl::class); + $this->_remoteCoverageHelper = new RemoteCoverageHelper($this->_remoteUrl); + } + + /** + * Test description. + * + * @param string $coverage_script_url Url. + * @param string $test_id Test ID. + * + * @return void + * @dataProvider createUrlErrorDataProvider + */ + public function testCreateUrlError($coverage_script_url, $test_id) + { + $this->expectException('InvalidArgumentException'); + + $this->_remoteCoverageHelper->get($coverage_script_url, $test_id); + } + + /** + * Returns url that end up badly. + * + * @return array + */ + public static function createUrlErrorDataProvider() + { + return array( + array('', 'test-id'), + array('coverage-url', ''), + array('', ''), + ); + } + + /** + * Test description. + * + * @param string $coverage_script_url Coverage script URL. + * @param string $test_id Test ID. + * @param string $expected_url Expected URL to be queried. + * + * @return void + * @dataProvider createUrlDataProvider + */ + public function testCreateUrl($coverage_script_url, $test_id, $expected_url) + { + $this->_remoteUrl + ->shouldReceive('getPageContent') + ->with($expected_url) + ->once() + ->andReturn(false); + + $result = $this->_remoteCoverageHelper->get($coverage_script_url, $test_id); + + if ( \class_exists(RawCodeCoverageData::class) ) { + $this->assertInstanceOf(RawCodeCoverageData::class, $result); + $this->assertCount(0, $result->lineCoverage()); + } + else { + $this->assertIsArray($result); + $this->assertCount(0, $result); + } + } + + /** + * Returns url that does valid call. + * + * @return array + */ + public static function createUrlDataProvider() + { + return array( + array('http://host', 'test-id', 'http://host?rct_mode=output&PHPUNIT_MINK_TEST_ID=test-id'), + array('http://host?p1=v1', 'test-id', 'http://host?p1=v1&rct_mode=output&PHPUNIT_MINK_TEST_ID=test-id'), + ); + } + + /** + * Test description. + * + * @return void + */ + public function testReturnedCoverageNotASerializedArray() + { + $this->expectException('RuntimeException'); + + $this->_remoteUrl + ->shouldReceive('getPageContent') + ->once() + ->andReturn(''); + + $this->_remoteCoverageHelper->get('A', 'B'); + } + + /** + * Test description. + * + * @return void + */ + public function testValidCoverageIsReturned() + { + $fixture_folder = __DIR__ . '/../Fixture'; + + $this->_remoteUrl + ->shouldReceive('getPageContent') + ->once() + ->andReturn(file_get_contents($fixture_folder . '/coverage_data.txt')); + + $content = $this->_remoteCoverageHelper->get('A', 'B'); + $class_source_file = realpath($fixture_folder . '/DummyClass.php'); + + $expected = array( + 3 => 1, + 6 => 1, + 7 => -2, + 11 => -1, + 12 => -2, + 14 => 1, + ); + + if ( \class_exists(RawCodeCoverageData::class) ) { + $this->assertEquals(array($class_source_file => $expected), $content->lineCoverage()); + } + else { + $this->assertEquals($expected, $content[$class_source_file]); + } + } + +} diff --git a/tests/aik099/PHPUnit/RemoteCoverage/RemoteUrlTest.php b/tests/aik099/PHPUnit/RemoteCoverage/RemoteUrlTest.php new file mode 100644 index 0000000..600cc90 --- /dev/null +++ b/tests/aik099/PHPUnit/RemoteCoverage/RemoteUrlTest.php @@ -0,0 +1,33 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\RemoteCoverage; + + +use aik099\PHPUnit\RemoteCoverage\RemoteUrl; +use tests\aik099\PHPUnit\AbstractTestCase; + +class RemoteUrlTest extends AbstractTestCase +{ + + /** + * Test description. + * + * @return void + */ + public function testGetPageContent() + { + $file = __DIR__ . '/../Fixture/coverage_data.txt'; + + $remote_url = new RemoteUrl(); + $this->assertEquals(file_get_contents($file), $remote_url->getPageContent($file)); + } + +} diff --git a/tests/aik099/PHPUnit/Session/AbstractSessionStrategyTestCase.php b/tests/aik099/PHPUnit/Session/AbstractSessionStrategyTestCase.php new file mode 100644 index 0000000..ffbff9a --- /dev/null +++ b/tests/aik099/PHPUnit/Session/AbstractSessionStrategyTestCase.php @@ -0,0 +1,41 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Session; + + +use aik099\PHPUnit\Session\ISessionStrategy; +use tests\aik099\PHPUnit\AbstractTestCase; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use Behat\Mink\Session; +use aik099\PHPUnit\BrowserTestCase; + +abstract class AbstractSessionStrategyTestCase extends AbstractTestCase +{ + + const BROWSER_CLASS = BrowserConfiguration::class; + + const SESSION_CLASS = Session::class; + + const TEST_CASE_CLASS = BrowserTestCase::class; + + /** + * Session strategy. + * + * @var ISessionStrategy + */ + protected $strategy; + + public function testUnknownSessionFreshnessStateUntilItsStarted() + { + $this->assertNull($this->strategy->isFreshSession()); + } + +} diff --git a/tests/aik099/PHPUnit/Session/IsolatedSessionStrategyTest.php b/tests/aik099/PHPUnit/Session/IsolatedSessionStrategyTest.php new file mode 100644 index 0000000..0528abb --- /dev/null +++ b/tests/aik099/PHPUnit/Session/IsolatedSessionStrategyTest.php @@ -0,0 +1,102 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Session; + + +use aik099\PHPUnit\Session\IsolatedSessionStrategy; +use Behat\Mink\Driver\DriverInterface; +use Behat\Mink\Session; +use Mockery as m; + +class IsolatedSessionStrategyTest extends AbstractSessionStrategyTestCase +{ + + /** + * @before + */ + protected function setUpTest() + { + $this->strategy = new IsolatedSessionStrategy(); + } + + /** + * Test description. + * + * @return void + */ + public function testSession() + { + $browser = m::mock(self::BROWSER_CLASS); + + $driver1 = m::mock(DriverInterface::class); + $driver1->shouldReceive('setSession')->with(m::type(Session::class))->once(); + + $driver2 = m::mock(DriverInterface::class); + $driver2->shouldReceive('setSession')->with(m::type(Session::class))->once(); + + $browser->shouldReceive('createDriver')->twice()->andReturn($driver1, $driver2); + + $session1 = $this->strategy->session($browser); + $this->assertInstanceOf(Session::class, $session1); + $this->assertSame($driver1, $session1->getDriver()); + + $session2 = $this->strategy->session($browser); + $this->assertInstanceOf(Session::class, $session2); + $this->assertSame($driver2, $session2->getDriver()); + } + + public function testIsFreshSessionAfterSessionIsStarted() + { + $driver = m::mock(DriverInterface::class); + $driver->shouldReceive('setSession')->with(m::type(Session::class))->once(); + + $browser = m::mock(self::BROWSER_CLASS); + $browser->shouldReceive('createDriver')->once()->andReturn($driver); + + $this->strategy->session($browser); + + $this->assertTrue($this->strategy->isFreshSession()); + } + + /** + * Test description. + * + * @return void + */ + public function testOnTestEnded() + { + $session = m::mock(self::SESSION_CLASS); + $session->shouldReceive('stop')->once(); + $session->shouldReceive('isStarted')->once()->andReturn(true); + + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getSession')->with(false)->once()->andReturn($session); + + $this->strategy->onTestEnded($test_case); + } + + public function testOnTestFailed() + { + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getSession')->never(); + + $this->strategy->onTestFailed($test_case, new \Exception('test')); + } + + public function testOnTestSuiteEnded() + { + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getSession')->never(); + + $this->strategy->onTestSuiteEnded($test_case); + } + +} diff --git a/tests/aik099/PHPUnit/Session/SessionStrategyFactoryTest.php b/tests/aik099/PHPUnit/Session/SessionStrategyFactoryTest.php new file mode 100644 index 0000000..e805edb --- /dev/null +++ b/tests/aik099/PHPUnit/Session/SessionStrategyFactoryTest.php @@ -0,0 +1,66 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Session; + + +use aik099\PHPUnit\Session\ISessionStrategy; +use aik099\PHPUnit\Session\SessionStrategyFactory; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; + +class SessionStrategyFactoryTest extends AbstractTestCase +{ + + use ExpectException; + + /** + * Session factory. + * + * @var SessionStrategyFactory + */ + private $_factory; + + /** + * @before + */ + protected function setUpTest() + { + $this->_factory = new SessionStrategyFactory(); + } + + public function testRegisterSuccess() + { + $session_strategy = m::mock(ISessionStrategy::class); + $this->_factory->register('strategy-type', $session_strategy); + + $this->assertInstanceOf(ISessionStrategy::class, $this->_factory->createStrategy('strategy-type')); + } + + public function testRegisterFailure() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Session strategy with type "strategy-type" is already registered'); + + $session_strategy = m::mock(ISessionStrategy::class); + $this->_factory->register('strategy-type', $session_strategy); + $this->_factory->register('strategy-type', $session_strategy); + } + + public function testCreateStrategyFailure() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Session strategy type "wrong" not registered'); + + $this->_factory->createStrategy('wrong'); + } + +} diff --git a/tests/aik099/PHPUnit/Session/SessionStrategyManagerTest.php b/tests/aik099/PHPUnit/Session/SessionStrategyManagerTest.php new file mode 100644 index 0000000..14c8d35 --- /dev/null +++ b/tests/aik099/PHPUnit/Session/SessionStrategyManagerTest.php @@ -0,0 +1,148 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Session; + + +use aik099\PHPUnit\Session\ISessionStrategy; +use aik099\PHPUnit\Session\ISessionStrategyFactory; +use aik099\PHPUnit\Session\SessionStrategyFactory; +use aik099\PHPUnit\Session\SessionStrategyManager; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use aik099\PHPUnit\Session\IsolatedSessionStrategy; +use aik099\PHPUnit\Session\SharedSessionStrategy; +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\BrowserTestCase; + +class SessionStrategyManagerTest extends AbstractTestCase +{ + + /** + * Session strategy manager. + * + * @var SessionStrategyManager + */ + protected $manager; + + /** + * Session strategy factory. + * + * @var SessionStrategyFactory + */ + protected $factory; + + /** + * @before + */ + protected function setUpTest() + { + $this->factory = m::mock(ISessionStrategyFactory::class); + $this->manager = new SessionStrategyManager($this->factory); + } + + /** + * Test description. + * + * @return void + */ + public function testGetDefaultSessionStrategy() + { + $expected = 'OK'; + $this->factory->shouldReceive('createStrategy')->andReturn($expected); + + $this->assertEquals($expected, $this->manager->getDefaultSessionStrategy()); + } + + /** + * Test description. + * + * @return void + */ + public function testGetDefaultSessionStrategySharing() + { + $this->factory->shouldReceive('createStrategy')->andReturn('OK'); + + $this->assertEquals($this->manager->getDefaultSessionStrategy(), $this->manager->getDefaultSessionStrategy()); + } + + /** + * Test description. + * + * @return void + */ + public function testGetSessionStrategySharing() + { + $this->factory + ->shouldReceive('createStrategy') + ->andReturnUsing(function () { + return m::mock(ISessionStrategy::class); + }); + + // Sequential identical browser configurations share strategy. + $strategy1 = $this->_getStrategy(ISessionStrategyFactory::TYPE_ISOLATED, 'H1'); + $strategy2 = $this->_getStrategy(ISessionStrategyFactory::TYPE_ISOLATED, 'H1'); + $this->assertSame($strategy1, $strategy2); + + // Different browser configuration use different strategy. + $strategy3 = $this->_getStrategy(ISessionStrategyFactory::TYPE_ISOLATED, 'H2'); + $this->assertNotSame($strategy2, $strategy3); + + // Different browser configuration break the sequence. + $strategy4 = $this->_getStrategy(ISessionStrategyFactory::TYPE_ISOLATED, 'H1'); + $this->assertNotSame($strategy1, $strategy4); + } + + /** + * Test description. + * + * @return void + */ + public function testGetSessionStrategyIsolated() + { + $expected = IsolatedSessionStrategy::class; + $this->factory->shouldReceive('createStrategy')->andReturn(m::mock($expected)); + + $this->assertInstanceOf($expected, $this->_getStrategy(ISessionStrategyFactory::TYPE_ISOLATED, 'IS1')); + } + + /** + * Test description. + * + * @return void + */ + public function testGetSessionStrategyShared() + { + $expected = SharedSessionStrategy::class; + $this->factory->shouldReceive('createStrategy')->andReturn(m::mock($expected)); + + $this->assertInstanceOf($expected, $this->_getStrategy(ISessionStrategyFactory::TYPE_SHARED, 'SH1')); + } + + /** + * Creates browser configuration. + * + * @param string $strategy_type Strategy type. + * @param string $strategy_hash Strategy hash. + * + * @return ISessionStrategy + */ + private function _getStrategy($strategy_type, $strategy_hash) + { + $browser = m::mock(BrowserConfiguration::class); + $browser->shouldReceive('getSessionStrategy')->once()->andReturn($strategy_type); + $browser->shouldReceive('getSessionStrategyHash')->once()->andReturn($strategy_hash); + + $test_case = m::mock(BrowserTestCase::class); + + return $this->manager->getSessionStrategy($browser, $test_case); + } + +} diff --git a/tests/aik099/PHPUnit/Session/SharedSessionStrategyTest.php b/tests/aik099/PHPUnit/Session/SharedSessionStrategyTest.php new file mode 100644 index 0000000..a8d5d58 --- /dev/null +++ b/tests/aik099/PHPUnit/Session/SharedSessionStrategyTest.php @@ -0,0 +1,222 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\Session; + + +use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; +use aik099\PHPUnit\Session\ISessionStrategy; +use aik099\PHPUnit\Session\IsolatedSessionStrategy; +use aik099\PHPUnit\Session\SharedSessionStrategy; +use Behat\Mink\Session; +use ConsoleHelpers\PHPUnitCompat\Framework\IncompleteTestError; +use ConsoleHelpers\PHPUnitCompat\Framework\SkippedTestError; +use Mockery as m; +use Mockery\MockInterface; + +class SharedSessionStrategyTest extends AbstractSessionStrategyTestCase +{ + + /** + * Isolated strategy. + * + * @var IsolatedSessionStrategy + */ + private $_originalStrategy; + + /** + * First created session. + * + * @var IsolatedSessionStrategy + */ + private $_session1; + + /** + * Second created session. + * + * @var IsolatedSessionStrategy + */ + private $_session2; + + /** + * @before + */ + protected function setUpTest() + { + $this->_session1 = $this->createSession(); + $this->_session2 = $this->createSession(); + + $this->_originalStrategy = m::mock(ISessionStrategy::class); + $this->strategy = new SharedSessionStrategy($this->_originalStrategy); + } + + /** + * Test description. + * + * @param \Exception $e Exception. + * + * @return void + * @dataProvider ignoreExceptionDataProvider + */ + public function testSessionSharing(\Exception $e = null) + { + /** @var BrowserConfiguration $browser */ + $browser = m::mock(self::BROWSER_CLASS); + $this->_originalStrategy->shouldReceive('session')->once()->with($browser)->andReturn($this->_session1); + $this->_originalStrategy->shouldReceive('isFreshSession')->once()->andReturn(true); + + $this->expectNoPopups($this->_session1); + + $this->assertSame($this->_session1, $this->strategy->session($browser)); + $this->assertTrue($this->strategy->isFreshSession(), 'First created session must be fresh'); + + if ( isset($e) ) { + $this->_sessionFailure($e); + } + + $this->assertSame($this->_session1, $this->strategy->session($browser)); + $this->assertFalse($this->strategy->isFreshSession(), 'Reused session must not be fresh'); + } + + /** + * Expects no popups. + * + * @param MockInterface $session Session. + * + * @return void + */ + protected function expectNoPopups(MockInterface $session) + { + // Testing if popup windows are actually closed will be done in the integration test. + $session->shouldReceive('switchToWindow')->atLeast()->once(); + $session->shouldReceive('getWindowName')->once()->andReturn('initial-window-name'); + $session->shouldReceive('getWindowNames')->once()->andReturn(array('initial-window-name')); + } + + /** + * Returns exceptions, that doesn't reset session. + * + * @return array + */ + public static function ignoreExceptionDataProvider() + { + return array( + 'no error' => array(null), + 'incomplete test' => array(new IncompleteTestError()), + 'skipped test' => array(new SkippedTestError()), + ); + } + + /** + * Test description. + * + * @return void + */ + public function testSessionResetOnFailure() + { + /** @var BrowserConfiguration $browser */ + $browser = m::mock(self::BROWSER_CLASS); + + $this->_originalStrategy + ->shouldReceive('session') + ->with($browser) + ->twice() + ->andReturn($this->_session1, $this->_session2); + $this->_originalStrategy + ->shouldReceive('isFreshSession') + ->twice() + ->andReturn(true); + + $this->_session1->shouldReceive('isStarted')->once()->andReturn(true); + $this->_session1->shouldReceive('stop')->once(); + $this->expectNoPopups($this->_session2); + + $session = $this->strategy->session($browser); + $this->assertSame($this->_session1, $session); + $this->assertTrue($this->strategy->isFreshSession(), 'First created session must be fresh'); + + $this->_sessionFailure(new \Exception()); + + $this->assertSame($this->_session2, $this->strategy->session($browser)); + $this->assertTrue($this->strategy->isFreshSession(), 'First created session after failure must be fresh'); + + $this->assertSame($this->_session2, $this->strategy->session($browser)); + $this->assertFalse($this->strategy->isFreshSession(), 'Reused session must not be fresh'); + } + + /** + * Test description. + * + * @return void + */ + public function testImmediateSessionFailure() + { + $this->_sessionFailure(new \Exception()); + + $this->_originalStrategy->shouldReceive('session')->once()->andReturn($this->_session1); + $this->_originalStrategy->shouldReceive('isFreshSession')->once()->andReturn(true); + + $this->assertSame($this->_session1, $this->strategy->session(m::mock(self::BROWSER_CLASS))); + $this->assertTrue($this->strategy->isFreshSession(), 'First created session after failure must be fresh'); + } + + /** + * Generates test failure. + * + * @param \Exception $e Exception. + * + * @return void + */ + private function _sessionFailure(\Exception $e) + { + $this->strategy->onTestFailed(m::mock(self::TEST_CASE_CLASS), $e); + } + + /** + * Test description. + * + * @return void + */ + public function testOnTestEnded() + { + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getSession')->never(); + + $this->strategy->onTestEnded($test_case); + } + + /** + * Test description. + * + * @return void + */ + public function testEndOfTestCaseWithSession() + { + $session = $this->createSession(); + $session->shouldReceive('stop')->withNoArgs()->withAnyArgs()->once(); + $session->shouldReceive('isStarted')->once()->andReturn(true); + + $test_case = m::mock(self::TEST_CASE_CLASS); + $test_case->shouldReceive('getSession')->with(false)->once()->andReturn($session); + + $this->strategy->onTestSuiteEnded($test_case); + } + + /** + * Creates session mock. + * + * @return Session|MockInterface + */ + protected function createSession() + { + return m::mock(self::SESSION_CLASS); + } + +} diff --git a/tests/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategyTest.php b/tests/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategyTest.php deleted file mode 100644 index 6fc07b0..0000000 --- a/tests/aik099/PHPUnit/SessionStrategy/IsolatedSessionStrategyTest.php +++ /dev/null @@ -1,129 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace tests\aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use aik099\PHPUnit\SessionStrategy\IsolatedSessionStrategy; -use Behat\Mink\Session; -use Mockery as m; - -class IsolatedSessionStrategyTest extends \PHPUnit_Framework_TestCase -{ - - const BROWSER_CLASS = '\\aik099\\PHPUnit\\BrowserConfiguration\\BrowserConfiguration'; - - const SESSION_CLASS = '\\Behat\\Mink\\Session'; - - /** - * Session strategy. - * - * @var IsolatedSessionStrategy - */ - protected $strategy; - - /** - * Creates session strategy. - * - * @return void - */ - protected function setUp() - { - parent::setUp(); - - $this->strategy = new IsolatedSessionStrategy(); - } - - /** - * Test description. - * - * @return void - */ - public function testSession() - { - $expected_session1 = $this->createSession(1); - $expected_session2 = $this->createSession(1); - - /* @var $browser BrowserConfiguration */ - $browser = m::mock(self::BROWSER_CLASS); - $browser->shouldReceive('createSession')->twice()->andReturn($expected_session1, $expected_session2); - - $session1 = $this->strategy->session($browser); - $session2 = $this->strategy->session($browser); - - $this->assertInstanceOf(self::SESSION_CLASS, $session1); - $this->assertSame($expected_session1, $session1); - - $this->assertInstanceOf(self::SESSION_CLASS, $session2); - $this->assertSame($expected_session2, $session2); - - $this->assertNotSame($session1, $session2); - } - - /** - * Test description. - * - * @return void - */ - public function testNotSuccessfulTest() - { - $this->assertSame($this->strategy, $this->strategy->notSuccessfulTest(new \Exception())); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTestWithSession() - { - $session = $this->createSession(0); - $session->shouldReceive('stop')->withNoArgs()->once()->andReturnNull(); - - $this->assertSame($this->strategy, $this->strategy->endOfTest($session)); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTestWithoutSession() - { - $this->assertSame($this->strategy, $this->strategy->endOfTest()); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTestCase() - { - $this->assertSame($this->strategy, $this->strategy->endOfTestCase()); - } - - /** - * Creates session. - * - * @param integer $start_count Session start time count. - * - * @return Session - */ - protected function createSession($start_count = 0) - { - $session = m::mock(self::SESSION_CLASS); - $session->shouldReceive('start')->withNoArgs()->times($start_count)->andReturnNull(); - - return $session; - } - -} diff --git a/tests/aik099/PHPUnit/SessionStrategy/SessionStrategyManagerTest.php b/tests/aik099/PHPUnit/SessionStrategy/SessionStrategyManagerTest.php deleted file mode 100644 index e60b324..0000000 --- a/tests/aik099/PHPUnit/SessionStrategy/SessionStrategyManagerTest.php +++ /dev/null @@ -1,146 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace tests\aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\SessionStrategy\SessionStrategyManager; -use Mockery as m; - -class SessionStrategyManagerTest extends \PHPUnit_Framework_TestCase -{ - - /** - * Session strategy manager. - * - * @var SessionStrategyManager - */ - protected $manager; - - /** - * Creates session strategy manager to use for tests. - * - * @return void - */ - protected function setUp() - { - $this->manager = SessionStrategyManager::getInstance(); - } - - /** - * Test description. - * - * @return void - */ - public function testGetInstance() - { - $this->assertSame(SessionStrategyManager::getInstance(), SessionStrategyManager::getInstance()); - } - - /** - * Test description. - * - * @return void - */ - public function testGetSessionStrategySharing() - { - // sequential identical browser configurations share strategy - $strategy1 = $this->manager->getSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY, 'H1'); - $strategy2 = $this->manager->getSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY, 'H1'); - $this->assertSame($strategy1, $strategy2); - - // different browser configuration use different strategy - $strategy3 = $this->manager->getSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY, 'H2'); - $this->assertNotSame($strategy2, $strategy3); - - // different browser configuration break the sequence - $strategy4 = $this->manager->getSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY, 'H1'); - $this->assertNotSame($strategy1, $strategy4); - } - - /** - * Test description. - * - * @return void - */ - public function testGetSessionStrategyIsolated() - { - $actual = $this->manager->getSessionStrategy(SessionStrategyManager::ISOLATED_STRATEGY, 'IS1'); - - $this->assertInstanceOf('\\aik099\\PHPUnit\\SessionStrategy\\IsolatedSessionStrategy', $actual); - } - - /** - * Test description. - * - * @return void - */ - public function testCreateSessionStrategyIsolated() - { - $expected = '\\aik099\\PHPUnit\\SessionStrategy\\IsolatedSessionStrategy'; - $this->assertInstanceOf($expected, $this->manager->createSessionStrategy(false)); - } - - /** - * Test description. - * - * @return void - */ - public function testGetSessionStrategyShared() - { - $actual = $this->manager->getSessionStrategy(SessionStrategyManager::SHARED_STRATEGY, 'SH1'); - - $this->assertInstanceOf('\\aik099\\PHPUnit\\SessionStrategy\\SharedSessionStrategy', $actual); - } - - /** - * Test description. - * - * @return void - */ - public function testCreateSessionStrategyShared() - { - $expected = '\\aik099\\PHPUnit\\SessionStrategy\\SharedSessionStrategy'; - $this->assertInstanceOf($expected, $this->manager->createSessionStrategy(true)); - } - - /** - * Test description. - * - * @return void - * @expectedException \InvalidArgumentException - */ - public function testCreateSessionStrategyIncorrect() - { - $this->manager->createSessionStrategy('wrong'); - } - - /** - * Test description. - * - * @return void - */ - public function testGetDefaultSessionStrategy() - { - $expected = '\\aik099\\PHPUnit\\SessionStrategy\\IsolatedSessionStrategy'; - $this->assertInstanceOf($expected, $this->manager->getDefaultSessionStrategy()); - } - - /** - * Test description. - * - * @return void - */ - public function testGetDefaultSessionStrategySharing() - { - $this->assertSame($this->manager->getDefaultSessionStrategy(), $this->manager->getDefaultSessionStrategy()); - } - -} diff --git a/tests/aik099/PHPUnit/SessionStrategy/SharedSessionStrategyTest.php b/tests/aik099/PHPUnit/SessionStrategy/SharedSessionStrategyTest.php deleted file mode 100644 index f63619d..0000000 --- a/tests/aik099/PHPUnit/SessionStrategy/SharedSessionStrategyTest.php +++ /dev/null @@ -1,169 +0,0 @@ - - * @link https://github.com/aik099/phpunit-mink - */ - -namespace tests\aik099\PHPUnit\SessionStrategy; - - -use aik099\PHPUnit\BrowserConfiguration\BrowserConfiguration; -use aik099\PHPUnit\SessionStrategy\IsolatedSessionStrategy; -use aik099\PHPUnit\SessionStrategy\SharedSessionStrategy; -use Behat\Mink\Session; -use Mockery as m; - -class SharedSessionStrategyTest extends \PHPUnit_Framework_TestCase -{ - - const BROWSER_CLASS = '\\aik099\\PHPUnit\\BrowserConfiguration\\BrowserConfiguration'; - - const SESSION_CLASS = '\\Behat\\Mink\\Session'; - - /** - * Session strategy. - * - * @var SharedSessionStrategy - */ - protected $strategy; - - /** - * Isolated strategy. - * - * @var IsolatedSessionStrategy - */ - protected $isolatedStrategy; - - /** - * First created session. - * - * @var IsolatedSessionStrategy - */ - protected $session1; - - /** - * Second created session. - * - * @var IsolatedSessionStrategy - */ - protected $session2; - - /** - * Creates session strategy. - * - * @return void - */ - protected function setUp() - { - parent::setUp(); - - $this->session1 = $this->createSession(); - $this->session2 = $this->createSession(); - - $this->isolatedStrategy = m::mock('\\aik099\\PHPUnit\\SessionStrategy\\IsolatedSessionStrategy'); - $this->strategy = new SharedSessionStrategy($this->isolatedStrategy); - } - - /** - * Test description. - * - * @return void - */ - public function testSessionSharing() - { - $browser = m::mock(self::BROWSER_CLASS); - /* @var $browser BrowserConfiguration */ - - $this->isolatedStrategy->shouldReceive('session')->once()->with($browser)->andReturn($this->session1); - - $this->assertSame($this->session1, $this->strategy->session($browser)); - $this->assertSame($this->session1, $this->strategy->session($browser)); - $this->assertSame($this->session1, $this->strategy->session($browser)); - } - - /** - * Test description. - * - * @return void - */ - public function testSessionResetOnFailure() - { - $browser = m::mock(self::BROWSER_CLASS); - /* @var $browser BrowserConfiguration */ - - $this->isolatedStrategy - ->shouldReceive('session')->twice()->with($browser)->andReturn($this->session1, $this->session2); - - $this->session1->shouldReceive('stop')->once()->andReturnNull(); - - $session = $this->strategy->session($browser); - $this->assertSame($this->session1, $session); - - $this->strategy->notSuccessfulTest(new \Exception()); - - $this->assertSame($this->session2, $this->strategy->session($browser)); - $this->assertSame($this->session2, $this->strategy->session($browser)); - } - - /** - * Test description. - * - * @return void - */ - public function testNotSuccessfulTest() - { - $this->assertSame($this->strategy, $this->strategy->notSuccessfulTest(new \Exception())); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTest() - { - $this->assertSame($this->strategy, $this->strategy->endOfTest()); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTestCaseWithSession() - { - $session = $this->createSession(); - $session->shouldReceive('stop')->withNoArgs()->once()->andReturnNull(); - - $this->assertSame($this->strategy, $this->strategy->endOfTestCase($session)); - } - - /** - * Test description. - * - * @return void - */ - public function testEndOfTestCaseWithoutSession() - { - $this->assertSame($this->strategy, $this->strategy->endOfTestCase()); - } - - /** - * Creates session mock. - * - * @return Session - */ - protected function createSession() - { - $session = m::mock(self::SESSION_CLASS); - $session->shouldReceive('getDriver')->andReturnNull(); - $session->shouldReceive('switchToWindow')->with(null)->andReturnNull(); - - return $session; - } - -} diff --git a/tests/aik099/PHPUnit/TVerifyTestExpectations.php b/tests/aik099/PHPUnit/TVerifyTestExpectations.php new file mode 100644 index 0000000..274193f --- /dev/null +++ b/tests/aik099/PHPUnit/TVerifyTestExpectations.php @@ -0,0 +1,37 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit; + + +trait TVerifyTestExpectations +{ + + /** + * @after + */ + protected function verifyMockeryExpectations() + { + if ( !\class_exists('Mockery') ) { + return; + } + + // Add Mockery expectations to assertion count. + $container = \Mockery::getContainer(); + + if ( $container !== null ) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + + // Verify Mockery expectations. + \Mockery::close(); + } + +} diff --git a/tests/aik099/PHPUnit/TestCase/ApplicationAwareTestCase.php b/tests/aik099/PHPUnit/TestCase/ApplicationAwareTestCase.php new file mode 100644 index 0000000..1596729 --- /dev/null +++ b/tests/aik099/PHPUnit/TestCase/ApplicationAwareTestCase.php @@ -0,0 +1,55 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\TestCase; + + +use aik099\PHPUnit\Application; +use Mockery as m; +use Mockery\MockInterface; +use tests\aik099\PHPUnit\AbstractTestCase; + +class ApplicationAwareTestCase extends AbstractTestCase +{ + + /** + * Application. + * + * @var Application|MockInterface + */ + protected $application; + + /** + * @before + */ + protected function setUpTest() + { + $this->application = m::mock(Application::class); + } + + /** + * Expects a factory call. + * + * @param string $service_id Service ID. + * @param mixed $returned_object Object to return. + * + * @return void + */ + protected function expectFactoryCall($service_id, $returned_object) + { + if ( is_array($returned_object) ) { + $this->application->shouldReceive('getObject')->with($service_id)->andReturnValues($returned_object); + } + else { + $this->application->shouldReceive('getObject')->with($service_id)->andReturn($returned_object); + } + } + +} diff --git a/tests/aik099/PHPUnit/TestSuite/BrowserTestSuiteTest.php b/tests/aik099/PHPUnit/TestSuite/BrowserTestSuiteTest.php new file mode 100644 index 0000000..9d00b1e --- /dev/null +++ b/tests/aik099/PHPUnit/TestSuite/BrowserTestSuiteTest.php @@ -0,0 +1,84 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\TestSuite\BrowserTestSuite; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use ConsoleHelpers\PHPUnitCompat\Framework\Test; + +class BrowserTestSuiteTest extends AbstractTestCase +{ + + /** + * Suite. + * + * @var BrowserTestSuite + */ + private $_suite; + + /** + * @before + */ + protected function setUpTest() + { + $this->_suite = new BrowserTestSuite(); + } + + /** + * Test description. + * + * @param array $browser Browser configuration array. + * @param string $expected_name Expected test name. + * + * @return void + * @dataProvider nameFromBrowserDataProvider + */ + public function testNameFromBrowser(array $browser, $expected_name) + { + $this->assertEquals($expected_name, $this->_suite->nameFromBrowser($browser)); + } + + /** + * Returns various browser configurations. + * + * @return array + */ + public static function nameFromBrowserDataProvider() + { + return array( + array(array('alias' => 'match'), 'match'), + array(array('alias' => 'match', 'browserName' => 'no-match'), 'match'), + array(array('browserName' => 'match'), 'match'), + array(array('browserName' => 'match', 'name' => 'no-match'), 'match'), + array(array('name' => 'match'), 'match'), + array(array(), 'undefined'), + ); + } + + /** + * Test description. + * + * @return void + */ + public function testSetBrowserFromConfiguration() + { + $browser = array('name' => 'safari'); + $test = m::mock(Test::class); + $test->shouldReceive('setBrowserFromConfiguration')->with($browser)->once(); + + $this->_suite->addTest($test); + + $this->assertSame($this->_suite, $this->_suite->setBrowserFromConfiguration($browser)); + } + +} diff --git a/tests/aik099/PHPUnit/TestSuite/RegularTestSuiteTest.php b/tests/aik099/PHPUnit/TestSuite/RegularTestSuiteTest.php new file mode 100644 index 0000000..badb265 --- /dev/null +++ b/tests/aik099/PHPUnit/TestSuite/RegularTestSuiteTest.php @@ -0,0 +1,76 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\TestSuite\RegularTestSuite; +use Mockery as m; +use tests\aik099\PHPUnit\AbstractTestCase; +use tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig; +use aik099\PHPUnit\Session\SessionStrategyManager; +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use ConsoleHelpers\PHPUnitCompat\Framework\Test; + +class RegularTestSuiteTest extends AbstractTestCase +{ + + /** + * Test description. + * + * @return void + */ + public function testAddTestMethods() + { + $suite = $this->_createSuite(); + + $actual = $suite->addTestMethods(WithoutBrowserConfig::class); + $this->assertSame($suite, $actual, 'The fluid interface doesn\'t work.'); + + $this->assertCount(2, $actual->tests(), 'Not all tests were added.'); + } + + /** + * Test description. + * + * @return void + */ + public function testSetTestDependencies() + { + $manager = m::mock(SessionStrategyManager::class); + $factory = m::mock(IBrowserConfigurationFactory::class); + $helper = m::mock(RemoteCoverageHelper::class); + + $test = m::mock(Test::class); + $test->shouldReceive('setSessionStrategyManager')->with($manager)->once(); + $test->shouldReceive('setBrowserConfigurationFactory')->with($factory)->once(); + $test->shouldReceive('setRemoteCoverageHelper')->with($helper)->once(); + + $suite = $this->_createSuite(); + $suite->addTest($test); + $this->assertSame( + $suite, + $suite->setTestDependencies($manager, $factory, $helper), + 'The fluid interface doesn\'t work.' + ); + } + + /** + * Creates suite. + * + * @return RegularTestSuite + */ + private function _createSuite() + { + return new RegularTestSuite(); + } + +} diff --git a/tests/aik099/PHPUnit/TestSuite/TestSuiteFactoryTest.php b/tests/aik099/PHPUnit/TestSuite/TestSuiteFactoryTest.php new file mode 100644 index 0000000..ac13fc5 --- /dev/null +++ b/tests/aik099/PHPUnit/TestSuite/TestSuiteFactoryTest.php @@ -0,0 +1,145 @@ + + * @link https://github.com/aik099/phpunit-mink + */ + +namespace tests\aik099\PHPUnit\TestSuite; + + +use aik099\PHPUnit\BrowserConfiguration\IBrowserConfigurationFactory; +use aik099\PHPUnit\RemoteCoverage\RemoteCoverageHelper; +use aik099\PHPUnit\Session\SessionStrategyManager; +use aik099\PHPUnit\TestSuite\BrowserTestSuite; +use aik099\PHPUnit\TestSuite\TestSuiteFactory; +use Mockery as m; +use tests\aik099\PHPUnit\TestCase\ApplicationAwareTestCase; +use aik099\PHPUnit\TestSuite\RegularTestSuite; +use tests\aik099\PHPUnit\Fixture\WithoutBrowserConfig; +use tests\aik099\PHPUnit\Fixture\WithBrowserConfig; + +class TestSuiteFactoryTest extends ApplicationAwareTestCase +{ + + /** + * Suite. + * + * @var TestSuiteFactory + */ + private $_factory; + + /** + * Session strategy manager. + * + * @var SessionStrategyManager + */ + private $_manager; + + /** + * Browser configuration factory. + * + * @var IBrowserConfigurationFactory + */ + private $_browserFactory; + + /** + * Remote coverage helper. + * + * @var RemoteCoverageHelper + */ + private $_remoteCoverageHelper; + + /** + * @before + */ + protected function setUpTest() + { + parent::setUpTest(); + + $this->_manager = m::mock(SessionStrategyManager::class); + $this->_browserFactory = m::mock(IBrowserConfigurationFactory::class); + $this->_remoteCoverageHelper = m::mock(RemoteCoverageHelper::class); + + $this->_factory = new TestSuiteFactory($this->_manager, $this->_browserFactory, $this->_remoteCoverageHelper); + $this->_factory->setApplication($this->application); + } + + /** + * Test description. + * + * @return void + */ + public function testCreateSuiteFromTestCaseWithoutBrowsers() + { + $suite_class_name = RegularTestSuite::class; + $test_case_class_name = WithoutBrowserConfig::class; + + $suite = m::mock($suite_class_name); + $suite->shouldReceive('setName')->with($test_case_class_name)->once(); + $suite->shouldReceive('addTestMethods')->with($test_case_class_name)->once(); + $suite + ->shouldReceive('setTestDependencies') + ->with($this->_manager, $this->_browserFactory, $this->_remoteCoverageHelper) + ->once(); + $this->expectFactoryCall('regular_test_suite', $suite); + + $actual_suite = $this->_factory->createSuiteFromTestCase($test_case_class_name); + $this->assertInstanceOf($suite_class_name, $actual_suite); + } + + /** + * Test description. + * + * @return void + */ + public function testCreateSuiteFromTestCaseWithBrowsers() + { + $suite_class_name = RegularTestSuite::class; + $test_case_class_name = WithBrowserConfig::class; + + $browser_suite1 = $this->_createBrowserTestSuiteMock($test_case_class_name, array( + 'browserName' => 'firefox', 'host' => 'localhost', + )); + $browser_suite2 = $this->_createBrowserTestSuiteMock($test_case_class_name, array( + 'browserName' => 'chrome', 'host' => '127.0.0.1', + )); + $this->expectFactoryCall('browser_test_suite', array($browser_suite1, $browser_suite2)); + + $suite = m::mock($suite_class_name); + $suite->shouldReceive('setName')->with($test_case_class_name)->once(); + $suite->shouldReceive('addTest')->with($browser_suite1)->once(); + $suite->shouldReceive('addTest')->with($browser_suite2)->once(); + $this->expectFactoryCall('regular_test_suite', $suite); + + $actual_suite = $this->_factory->createSuiteFromTestCase($test_case_class_name); + $this->assertInstanceOf($suite_class_name, $actual_suite); + } + + /** + * Creates browser suite mock. + * + * @param string $class_name Descendant of TestCase class. + * @param array $browser Browser configuration. + * + * @return BrowserTestSuite + */ + private function _createBrowserTestSuiteMock($class_name, array $browser) + { + $suite = m::mock(BrowserTestSuite::class); + $suite->shouldReceive('nameFromBrowser')->with($browser)->once()->andReturn('OK'); + $suite->shouldReceive('setName')->with($class_name . ': OK')->once(); + $suite->shouldReceive('addTestMethods')->with($class_name)->once(); + $suite + ->shouldReceive('setTestDependencies') + ->with($this->_manager, $this->_browserFactory, $this->_remoteCoverageHelper) + ->once(); + $suite->shouldReceive('setBrowserFromConfiguration')->with($browser)->once(); + + return $suite; + } + +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e980713..3824972 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,25 +1,5 @@ - * @link https://github.com/aik099/phpunit-mink - */ -define('FULL_PATH', realpath(__DIR__ . '/..')); +require_once __DIR__ . '/../vendor/autoload.php'; -$vendor_path = FULL_PATH . '/vendor'; - -if ( !is_dir($vendor_path) ) { - echo 'Install dependencies first' . PHP_EOL; - exit(1); -} - -require_once ($vendor_path . '/autoload.php'); - -$auto_loader = new \Composer\Autoload\ClassLoader(); -$auto_loader->add("aik099\\", FULL_PATH . '/library/'); -$auto_loader->add("tests\\aik099\\", FULL_PATH . '/'); -$auto_loader->register(); +define('BUILD_NAME', 'PHPUnit-Mink ' . \date('Y-m-d H:i'));